New design for Conversation Activity.
1) Move to Fragments for the list view. 2) Switch to CursorLoader from my jankey self-managed cursor. 3) Add session security logic to the ActionBar. 4) Fix colors to be less ugly.
@ -30,7 +30,7 @@
|
||||
|
||||
<activity android:name=".AutoInitiateActivity" android:theme="@android:style/Theme.Dialog" android:label="TextSecure Messaging Detected" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
|
||||
<activity android:name=".ApplicationPreferencesActivity" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
|
||||
<activity android:name=".ComposeMessageActivity" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
|
||||
<activity android:name=".ConversationActivity" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
|
||||
<activity android:name=".PassphraseCreateActivity" android:theme="@android:style/Theme.Dialog" android:label="Create Passphrase" android:launchMode="singleInstance" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
|
||||
<activity android:name=".PassphrasePromptActivity" android:theme="@android:style/Theme.Dialog" android:label="Enter Passphrase" android:launchMode="singleInstance" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
|
||||
<activity android:name=".PassphraseChangeActivity" android:theme="@android:style/Theme.Dialog" android:label="Change Passphrase" android:launchMode="singleInstance" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout"></activity>
|
||||
|
BIN
res/drawable-hdpi/ic_menu_attach.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-hdpi/ic_menu_call.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-hdpi/ic_menu_lock_holo_dark.png
Normal file
After Width: | Height: | Size: 765 B |
BIN
res/drawable-hdpi/ic_menu_lock_unverified_holo_dark.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
res/drawable-hdpi/ic_menu_lock_verified_holo_dark.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
res/drawable-mdpi/ic_menu_attach.png
Normal file
After Width: | Height: | Size: 993 B |
BIN
res/drawable-mdpi/ic_menu_call.png
Normal file
After Width: | Height: | Size: 861 B |
BIN
res/drawable-mdpi/ic_menu_lock_holo_dark.png
Normal file
After Width: | Height: | Size: 606 B |
BIN
res/drawable-mdpi/ic_menu_lock_unverified_holo_dark.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-mdpi/ic_menu_lock_verified_holo_dark.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-xhdpi/ic_menu_attach.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
res/drawable-xhdpi/ic_menu_call.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-xhdpi/ic_menu_lock_holo_dark.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-xhdpi/ic_menu_lock_unverified_holo_dark.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
res/drawable-xhdpi/ic_menu_lock_verified_holo_dark.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
@ -1,274 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Copyright (C) 2008 Esmertec AG.
|
||||
* 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.
|
||||
*/
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout android:layout_width="fill_parent"
|
||||
android:layout_height="43dip"
|
||||
android:background="@drawable/iphone_bar_top">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/secure_indicator"
|
||||
android:visibility="gone"
|
||||
android:paddingLeft="5.0dip"
|
||||
android:paddingRight="2.0dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_lock_iphone"
|
||||
android:adjustViewBounds="false"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/secure_indicator_yellow"-->
|
||||
<!-- android:visibility="gone"-->
|
||||
<!-- android:paddingLeft="5.0dip" -->
|
||||
<!-- android:paddingRight="2.0dip" -->
|
||||
<!-- android:layout_width="wrap_content" -->
|
||||
<!-- android:layout_height="wrap_content" -->
|
||||
<!-- android:src="@drawable/ic_lock_iphone_yellow2" -->
|
||||
<!-- android:adjustViewBounds="false" -->
|
||||
<!-- android:layout_alignParentLeft="true" -->
|
||||
<!-- android:layout_centerVertical="true" />-->
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/secure_indicator_red"
|
||||
android:visibility="gone"
|
||||
android:paddingLeft="5.0dip"
|
||||
android:paddingRight="2.0dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_lock_iphone_red2"
|
||||
android:adjustViewBounds="false"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/secure_indicator_green"-->
|
||||
<!-- android:visibility="gone"-->
|
||||
<!-- android:paddingLeft="5.0dip" -->
|
||||
<!-- android:paddingRight="2.0dip" -->
|
||||
<!-- android:layout_width="wrap_content" -->
|
||||
<!-- android:layout_height="wrap_content" -->
|
||||
<!-- android:src="@drawable/ic_lock_iphone_red2" -->
|
||||
<!-- android:adjustViewBounds="false" -->
|
||||
<!-- android:layout_alignParentLeft="true" -->
|
||||
<!-- android:layout_centerVertical="true" />-->
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/secure_indicator_question"-->
|
||||
<!-- android:visibility="gone"-->
|
||||
<!-- android:paddingLeft="0.0dip"-->
|
||||
<!-- android:paddingRight="2.0dip"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:src="@drawable/ic_lock_iphone_question"-->
|
||||
<!-- android:adjustViewBounds="false"-->
|
||||
<!-- android:layout_toRightOf="@id/secure_indicator_green"-->
|
||||
<!-- android:layout_centerVertical="true" />-->
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_bar"
|
||||
android:textSize="18.0dip"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#ffffffff"
|
||||
android:singleLine="true"
|
||||
style="?android:windowTitleStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/settings_button"-->
|
||||
<!-- android:paddingLeft="2.0dip" -->
|
||||
<!-- android:paddingRight="5.0dip" -->
|
||||
<!-- android:layout_width="wrap_content" -->
|
||||
<!-- android:layout_height="wrap_content" -->
|
||||
<!-- android:src="@drawable/ic_settings_iphone" -->
|
||||
<!-- android:adjustViewBounds="false" -->
|
||||
<!-- android:layout_alignParentRight="true" -->
|
||||
<!-- android:layout_centerVertical="true" />-->
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.components.RecipientsPanel
|
||||
android:id="@+id/recipients"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/white_background"
|
||||
android:gravity="bottom">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/conversation"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1.0"
|
||||
android:listSelector="@drawable/chat_history_selector"
|
||||
android:drawSelectorOnTop="true"
|
||||
android:transcriptMode="alwaysScroll"
|
||||
android:scrollbarAlwaysDrawVerticalTrack="true"
|
||||
android:scrollbarStyle="insideInset"
|
||||
android:stackFromBottom="true"
|
||||
android:visibility="gone"
|
||||
android:fadingEdge="none"
|
||||
android:layout_marginBottom="1dip"
|
||||
android:cacheColorHint="@android:color/white"
|
||||
android:dividerHeight="0px" android:divider="#ffffffff"/>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="5dip"
|
||||
android:background="@drawable/bottombar_landscape_565">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/attachment_editor"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/attachment_thumbnail"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="150dip"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- <Button-->
|
||||
<!-- android:id="@+id/view_image_button"-->
|
||||
<!-- style="?android:attr/buttonStyleSmall"-->
|
||||
<!-- android:layout_width="100dip"-->
|
||||
<!-- android:layout_height="50dip"-->
|
||||
<!-- android:text="View" />-->
|
||||
<!-- -->
|
||||
<!-- <Button-->
|
||||
<!-- android:id="@+id/replace_image_button"-->
|
||||
<!-- style="?android:attr/buttonStyleSmall"-->
|
||||
<!-- android:layout_width="100dip"-->
|
||||
<!-- android:layout_height="50dip"-->
|
||||
<!-- android:text="Replace" />-->
|
||||
|
||||
<Button
|
||||
android:id="@+id/remove_image_button"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="100dip"
|
||||
android:layout_height="50dip"
|
||||
android:text="Remove" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_panel"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dip"
|
||||
android:paddingLeft="5dip"
|
||||
android:paddingRight="5dip"
|
||||
>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/editor_with_counter"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:addStatesFromChildren="true"
|
||||
android:background="@android:drawable/edit_text">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/embedded_text_editor"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoText="true"
|
||||
android:capitalize="sentences"
|
||||
android:nextFocusRight="@+id/send_button"
|
||||
android:hint="Type to compose"
|
||||
android:maxLines="4"
|
||||
android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine"
|
||||
android:imeOptions="actionSend|flagNoEnterAction"
|
||||
android:background="@null"
|
||||
android:maxLength="1000"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_counter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#88000000"
|
||||
android:textColor="#ffffffff"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingLeft="3dip"
|
||||
android:paddingRight="3dip"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
<Button android:id="@+id/send_button"
|
||||
android:layout_marginLeft="5dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
style="?android:attr/buttonStyle"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:nextFocusLeft="@+id/embedded_text_editor"
|
||||
android:text="Send"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<TextView android:id="@+id/space_left"
|
||||
android:paddingLeft="5dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="160/160 (1)"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
210
res/layout/conversation_activity.xml
Normal file
@ -0,0 +1,210 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Copyright (C) 2008 Esmertec AG.
|
||||
* 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.
|
||||
*/
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.RecipientsPanel
|
||||
android:id="@+id/recipients"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/layout_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/white_background"
|
||||
android:gravity="bottom">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/fragment_content"
|
||||
android:name="org.thoughtcrime.securesms.ConversationFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@+id/bottom_container" />
|
||||
|
||||
<!-- <ListView -->
|
||||
<!-- android:id="@+id/conversation" -->
|
||||
<!-- android:layout_width="fill_parent" -->
|
||||
<!-- android:layout_height="fill_parent" -->
|
||||
<!-- android:layout_weight="1.0" -->
|
||||
<!-- android:listSelector="@drawable/chat_history_selector" -->
|
||||
<!-- android:drawSelectorOnTop="true" -->
|
||||
<!-- android:transcriptMode="alwaysScroll" -->
|
||||
<!-- android:scrollbarAlwaysDrawVerticalTrack="true" -->
|
||||
<!-- android:scrollbarStyle="insideInset" -->
|
||||
<!-- android:stackFromBottom="true" -->
|
||||
<!-- android:visibility="gone" -->
|
||||
<!-- android:fadingEdge="none" -->
|
||||
<!-- android:layout_marginBottom="1dip" -->
|
||||
<!-- android:cacheColorHint="@android:color/white" -->
|
||||
<!-- android:dividerHeight="0px" android:divider="#ffffffff"/> -->
|
||||
|
||||
<ScrollView android:id="@id/bottom_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="5dip"
|
||||
android:background="#fff">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/attachment_editor"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/attachment_thumbnail"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="150dip"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- <Button-->
|
||||
<!-- android:id="@+id/view_image_button"-->
|
||||
<!-- style="?android:attr/buttonStyleSmall"-->
|
||||
<!-- android:layout_width="100dip"-->
|
||||
<!-- android:layout_height="50dip"-->
|
||||
<!-- android:text="View" />-->
|
||||
<!-- -->
|
||||
<!-- <Button-->
|
||||
<!-- android:id="@+id/replace_image_button"-->
|
||||
<!-- style="?android:attr/buttonStyleSmall"-->
|
||||
<!-- android:layout_width="100dip"-->
|
||||
<!-- android:layout_height="50dip"-->
|
||||
<!-- android:text="Replace" />-->
|
||||
|
||||
<Button
|
||||
android:id="@+id/remove_image_button"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="100dip"
|
||||
android:layout_height="50dip"
|
||||
android:text="Remove" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View android:background="#eeeeee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_panel"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dip"
|
||||
android:paddingLeft="5dip"
|
||||
android:paddingRight="5dip"
|
||||
>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/embedded_text_editor"
|
||||
android:textColor="@android:color/black"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:autoText="true"
|
||||
android:capitalize="sentences"
|
||||
android:nextFocusRight="@+id/send_button"
|
||||
android:hint="Type message"
|
||||
android:maxLines="4"
|
||||
android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine"
|
||||
android:imeOptions="actionSend|flagNoEnterAction"
|
||||
android:maxLength="1000"
|
||||
/>
|
||||
|
||||
<!-- <RelativeLayout -->
|
||||
<!-- android:id="@+id/editor_with_counter" -->
|
||||
<!-- android:layout_width="0dip" -->
|
||||
<!-- android:layout_height="wrap_content" -->
|
||||
<!-- android:layout_weight="1.0" -->
|
||||
<!-- android:addStatesFromChildren="true" -->
|
||||
<!-- android:background="@android:drawable/edit_text"> -->
|
||||
|
||||
<!-- <EditText -->
|
||||
<!-- android:id="@+id/embedded_text_editor" -->
|
||||
<!-- android:layout_width="fill_parent" -->
|
||||
<!-- android:layout_height="wrap_content" -->
|
||||
<!-- android:autoText="true" -->
|
||||
<!-- android:capitalize="sentences" -->
|
||||
<!-- android:nextFocusRight="@+id/send_button" -->
|
||||
<!-- android:hint="Type to compose" -->
|
||||
<!-- android:maxLines="4" -->
|
||||
<!-- android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine" -->
|
||||
<!-- android:imeOptions="actionSend|flagNoEnterAction" -->
|
||||
<!-- android:background="@null" -->
|
||||
<!-- android:maxLength="1000" -->
|
||||
<!-- /> -->
|
||||
|
||||
<!-- <TextView -->
|
||||
<!-- android:id="@+id/text_counter" -->
|
||||
<!-- android:layout_width="wrap_content" -->
|
||||
<!-- android:layout_height="wrap_content" -->
|
||||
<!-- android:background="#88000000" -->
|
||||
<!-- android:textColor="#ffffffff" -->
|
||||
<!-- android:textSize="11sp" -->
|
||||
<!-- android:textStyle="bold" -->
|
||||
<!-- android:paddingLeft="3dip" -->
|
||||
<!-- android:paddingRight="3dip" -->
|
||||
<!-- android:layout_alignParentRight="true" -->
|
||||
<!-- android:layout_alignParentTop="true" -->
|
||||
<!-- android:visibility="gone" -->
|
||||
<!-- /> -->
|
||||
<!-- </RelativeLayout> -->
|
||||
|
||||
<Button android:id="@+id/send_button"
|
||||
android:layout_marginLeft="5dip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
style="?android:attr/buttonStyle"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:nextFocusLeft="@+id/embedded_text_editor"
|
||||
android:text="Send"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<TextView android:id="@+id/space_left"
|
||||
android:paddingLeft="5dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="160/160 (1)"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
21
res/layout/conversation_fragment.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0"
|
||||
android:listSelector="@drawable/chat_history_selector"
|
||||
android:drawSelectorOnTop="true"
|
||||
android:transcriptMode="alwaysScroll"
|
||||
android:scrollbarAlwaysDrawVerticalTrack="true"
|
||||
android:scrollbarStyle="insideInset"
|
||||
android:stackFromBottom="true"
|
||||
android:fadingEdge="none"
|
||||
android:layout_marginBottom="1dip"/>
|
||||
|
||||
</LinearLayout>
|
17
res/menu/conversation.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:title="Call"
|
||||
android:id="@+id/menu_call"
|
||||
android:icon="@drawable/ic_menu_call"
|
||||
android:showAsAction="ifRoom" />
|
||||
|
||||
<item android:title="Add attachment"
|
||||
android:id="@+id/menu_add_attachment"
|
||||
android:icon="@drawable/ic_menu_attach" />
|
||||
|
||||
<item android:title="Delete thread"
|
||||
android:id="@+id/menu_delete_thread"
|
||||
android:icon="@android:drawable/ic_menu_delete" />
|
||||
|
||||
</menu>
|
5
res/menu/conversation_button_context.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="Send unencrypted"
|
||||
android:id="@+id/menu_context_send_unencrypted" />
|
||||
</menu>
|
14
res/menu/conversation_context.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="Copy text"
|
||||
android:id="@+id/menu_context_copy" />
|
||||
|
||||
<item android:title="Delete message"
|
||||
android:id="@+id/menu_context_delete_message" />
|
||||
|
||||
<item android:title="Message details"
|
||||
android:id="@+id/menu_context_details" />
|
||||
|
||||
<item android:title="Forward message"
|
||||
android:id="@+id/menu_context_forward" />
|
||||
</menu>
|
13
res/menu/conversation_insecure.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="Security"
|
||||
android:id="@+id/menu_security"
|
||||
android:icon="@drawable/ic_menu_lock_holo_dark"
|
||||
android:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<item android:title="Start Secure Session"
|
||||
android:id="@+id/menu_start_secure_session" />
|
||||
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
19
res/menu/conversation_secure_unverified.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="Security"
|
||||
android:id="@+id/menu_security"
|
||||
android:icon="@drawable/ic_menu_lock_unverified_holo_dark"
|
||||
android:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<item android:title="Verify Session"
|
||||
android:id="@+id/menu_verify_session" />
|
||||
|
||||
<item android:title="Verify Recipient"
|
||||
android:id="@+id/menu_verify_recipient"/>
|
||||
|
||||
<item android:title="Abort Secure Session"
|
||||
android:id="@+id/menu_abort_session"/>
|
||||
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
19
res/menu/conversation_secure_verified.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="Security"
|
||||
android:id="@+id/menu_security"
|
||||
android:icon="@drawable/ic_menu_lock_unverified_holo_dark"
|
||||
android:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<item android:title="Verify Session"
|
||||
android:id="@+id/menu_verify_session" />
|
||||
|
||||
<item android:title="Verify Recipient"
|
||||
android:id="@+id/menu_verify_recipient"/>
|
||||
|
||||
<item android:title="Abort Secure Session"
|
||||
android:id="@+id/menu_abort_session"/>
|
||||
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
@ -1,830 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.thoughtcrime.securesms.components.RecipientsPanel;
|
||||
import org.thoughtcrime.securesms.crypto.AuthenticityCalculator;
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.thoughtcrime.securesms.crypto.KeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.SessionRecord;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentTypeSelectorAdapter;
|
||||
import org.thoughtcrime.securesms.mms.MediaTooLargeException;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.service.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.CharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.EncryptedCharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.InvalidMessageException;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.ClipboardManager;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.Window;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* Activity for displaying a message thread, as well as
|
||||
* composing/sending a new message into that thread.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class ComposeMessageActivity extends Activity {
|
||||
|
||||
private static final int PICK_CONTACT = 1;
|
||||
private static final int PICK_IMAGE = 2;
|
||||
private static final int PICK_VIDEO = 3;
|
||||
private static final int PICK_AUDIO = 4;
|
||||
|
||||
private static final int MENU_OPTION_CALL = 2;
|
||||
// private static final int MENU_OPTION_VIEW_CONTACT = 3;
|
||||
private static final int MENU_OPTION_VERIFY_KEYS = 4;
|
||||
private static final int MENU_OPTION_DELETE_THREAD = 5;
|
||||
private static final int MENU_OPTION_START_SESSION = 6;
|
||||
private static final int MENU_OPTION_DELETE_KEYS = 7;
|
||||
private static final int MENU_OPTION_ADD_ATTACHMENT = 8;
|
||||
private static final int MENU_OPTION_DETAILS = 9;
|
||||
private static final int MENU_OPTION_VERIFY_IDENTITY = 10;
|
||||
private static final int MENU_OPTION_REDECRYPT = 11;
|
||||
|
||||
private static final int MENU_OPTION_COPY = 100;
|
||||
private static final int MENU_OPTION_DELETE = 101;
|
||||
private static final int MENU_OPTION_FORWARD = 102;
|
||||
private static final int MENU_OPTION_SEND_CLEARTEXT = 103;
|
||||
private static final int MENU_OPTION_SEND_DELAYED = 104;
|
||||
|
||||
private static final int MESSAGE_ITEM_GROUP = 0;
|
||||
private static final int SEND_BUTTON_GROUP = 1;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private ConversationAdapter conversationAdapter;
|
||||
private ListView conversationView;
|
||||
private RecipientsPanel recipientsPanel;
|
||||
private EditText composeText;
|
||||
private ImageButton addContactButton;
|
||||
private Button sendButton;
|
||||
private TextView charactersLeft;
|
||||
private TextView titleBar;
|
||||
|
||||
private View greyLock;
|
||||
private View redLock;
|
||||
|
||||
private AttachmentTypeSelectorAdapter attachmentAdapter;
|
||||
private AttachmentManager attachmentManager;
|
||||
private KillActivityReceiver killActivityReceiver;
|
||||
private SecurityUpdateReceiver securityUpdateReceiver;
|
||||
|
||||
private Recipients recipients;
|
||||
private long threadId;
|
||||
private boolean sendEncrypted;
|
||||
private CharacterCalculator characterCalculator = new CharacterCalculator();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
Log.w("ComposeMessageActivity", "onCreate called...");
|
||||
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.compose_message_activity);
|
||||
|
||||
initializeReceivers();
|
||||
initializeResources();
|
||||
initializeTitleBar();
|
||||
initializeColors();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
Log.w("ComposeMessageActivity", "onResume called...");
|
||||
initializeSecurity(recipients);
|
||||
initializeTitleBar();
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
Log.w("ComposeMessageActivity", "onStart called...");
|
||||
if (isExistingConversation()) initializeConversationAdapter();
|
||||
else initializeRecipientsInput();
|
||||
|
||||
registerPassphraseActivityStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
Log.w("ComposeMessageActivity", "onStop called...");
|
||||
if (this.conversationAdapter != null)
|
||||
this.conversationAdapter.close();
|
||||
|
||||
registerPassphraseActivityStopped();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
unregisterReceiver(killActivityReceiver);
|
||||
unregisterReceiver(securityUpdateReceiver);
|
||||
MemoryCleaner.clean(masterSecret);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int reqCode, int resultCode, Intent data) {
|
||||
Log.w("ComposeMessageActivity", "onActivityResult called: " + resultCode + " , " + data);
|
||||
super.onActivityResult(reqCode, resultCode, data);
|
||||
|
||||
if (data == null || resultCode != Activity.RESULT_OK)
|
||||
return;
|
||||
|
||||
switch (reqCode) {
|
||||
case PICK_CONTACT:
|
||||
Recipients recipients = (Recipients)data.getParcelableExtra("recipients");
|
||||
|
||||
if (recipients != null)
|
||||
recipientsPanel.addRecipients(recipients);
|
||||
|
||||
break;
|
||||
case PICK_IMAGE:
|
||||
addAttachmentImage(data.getData());
|
||||
break;
|
||||
case PICK_VIDEO:
|
||||
addAttachmentVideo(data.getData());
|
||||
break;
|
||||
case PICK_AUDIO:
|
||||
addAttachmentAudio(data.getData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
menu.clear();
|
||||
|
||||
if (recipients != null && recipients.isSingleRecipient())
|
||||
menu.add(0, MENU_OPTION_CALL, Menu.NONE, "Call").setIcon(android.R.drawable.ic_menu_call);
|
||||
|
||||
menu.add(0, MENU_OPTION_DELETE_THREAD, Menu.NONE, "Delete Thread").setIcon(android.R.drawable.ic_menu_delete);
|
||||
menu.add(0, MENU_OPTION_ADD_ATTACHMENT, Menu.NONE, "Add Attachment").setIcon(R.drawable.ic_menu_attachment);
|
||||
|
||||
if (recipients != null && recipients.isSingleRecipient() && SessionRecord.hasSession(this, recipients.getPrimaryRecipient())) {
|
||||
SubMenu secureSettingsMenu = menu.addSubMenu("Secure Session Options").setIcon(android.R.drawable.ic_menu_more);
|
||||
secureSettingsMenu.add(0, MENU_OPTION_VERIFY_KEYS, Menu.NONE, "Verify Secure Session").setIcon(R.drawable.ic_lock_message_sms);
|
||||
secureSettingsMenu.add(0, MENU_OPTION_VERIFY_IDENTITY, Menu.NONE, "Verify Recipient Identity").setIcon(android.R.drawable.ic_menu_zoom);
|
||||
secureSettingsMenu.add(0, MENU_OPTION_DELETE_KEYS, Menu.NONE, "Abort Secure Session").setIcon(android.R.drawable.ic_menu_revert);
|
||||
} else if (recipients != null && recipients.isSingleRecipient()) {
|
||||
menu.add(0, MENU_OPTION_START_SESSION, Menu.NONE, "Start Secure Session").setIcon(R.drawable.ic_lock_message_sms);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case MENU_OPTION_CALL: dial(recipients.getPrimaryRecipient()); return true;
|
||||
case MENU_OPTION_DELETE_THREAD: deleteThread(); return true;
|
||||
case MENU_OPTION_VERIFY_KEYS: verifyKeys(); return true;
|
||||
case MENU_OPTION_START_SESSION: initiateSecureSession(); return true;
|
||||
case MENU_OPTION_DELETE_KEYS: abortSecureSession(); return true;
|
||||
case MENU_OPTION_ADD_ATTACHMENT: addAttachment(); return true;
|
||||
case MENU_OPTION_VERIFY_IDENTITY: verifyIdentity(); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
if (v == sendButton) createSendButtonContextMenu(menu);
|
||||
else createMessageItemContextMenu(menu);
|
||||
}
|
||||
|
||||
private void createSendButtonContextMenu(ContextMenu menu) {
|
||||
if (sendEncrypted)
|
||||
menu.add(SEND_BUTTON_GROUP, MENU_OPTION_SEND_CLEARTEXT, Menu.NONE, "Send unencrypted");
|
||||
|
||||
}
|
||||
|
||||
private void createMessageItemContextMenu(ContextMenu menu) {
|
||||
menu.add(MESSAGE_ITEM_GROUP, MENU_OPTION_COPY, Menu.NONE, "Copy text");
|
||||
menu.add(MESSAGE_ITEM_GROUP, MENU_OPTION_DELETE, Menu.NONE, "Delete");
|
||||
menu.add(MESSAGE_ITEM_GROUP, MENU_OPTION_DETAILS, Menu.NONE, "Message Details");
|
||||
menu.add(MESSAGE_ITEM_GROUP, MENU_OPTION_FORWARD, Menu.NONE, "Forward message");
|
||||
|
||||
Cursor cursor = ((CursorAdapter)conversationAdapter).getCursor();
|
||||
ConversationItem conversationItem = (ConversationItem)(conversationAdapter.newView(this, cursor, null));
|
||||
MessageRecord messageRecord = conversationItem.getMessageRecord();
|
||||
|
||||
if (messageRecord.isFailedDecryptType())
|
||||
menu.add(MESSAGE_ITEM_GROUP, MENU_OPTION_REDECRYPT, Menu.NONE, "Attempt decrypt again");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
if (item.getGroupId() == MESSAGE_ITEM_GROUP) return onMessageContextItemSelected(item);
|
||||
else if (item.getGroupId() == SEND_BUTTON_GROUP) return onSendContextItemSelected(item);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean onSendContextItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case MENU_OPTION_SEND_CLEARTEXT: sendMessage(false); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean onMessageContextItemSelected(MenuItem item) {
|
||||
Cursor cursor = ((CursorAdapter)conversationAdapter).getCursor();
|
||||
ConversationItem conversationItem = (ConversationItem)(conversationAdapter.newView(this, cursor, null));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
||||
MessageRecord messageRecord = conversationItem.getMessageRecord();
|
||||
|
||||
switch(item.getItemId()) {
|
||||
case MENU_OPTION_COPY: copyMessageToClipboard(messageRecord); return true;
|
||||
case MENU_OPTION_DELETE: deleteMessage(messageRecord); return true;
|
||||
case MENU_OPTION_DETAILS: displayMessageDetails(messageRecord); return true;
|
||||
case MENU_OPTION_REDECRYPT: redecryptMessage(messageRecord, address, body); return true;
|
||||
case MENU_OPTION_FORWARD: forwardMessage(messageRecord); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void verifyIdentity() {
|
||||
Intent verifyIdentityIntent = new Intent(this, VerifyIdentityActivity.class);
|
||||
verifyIdentityIntent.putExtra("recipient", recipients.getPrimaryRecipient());
|
||||
verifyIdentityIntent.putExtra("master_secret", masterSecret);
|
||||
startActivity(verifyIdentityIntent);
|
||||
}
|
||||
|
||||
private void verifyKeys() {
|
||||
Intent verifyKeysIntent = new Intent(this, VerifyKeysActivity.class);
|
||||
verifyKeysIntent.putExtra("recipient", recipients.getPrimaryRecipient());
|
||||
verifyKeysIntent.putExtra("master_secret", masterSecret);
|
||||
startActivity(verifyKeysIntent);
|
||||
}
|
||||
|
||||
private void initiateSecureSession() {
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
String recipientName = (recipient.getName() == null ? recipient.getNumber() : recipient.getName());
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Initiate Secure Session?");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage("Initiate secure session with " + recipientName + "?");
|
||||
builder.setPositiveButton(R.string.yes, new InitiateSecureSessionListener());
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void abortSecureSession() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Abort Secure Session Confirmation");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage("Are you sure that you want to abort this secure session?");
|
||||
builder.setPositiveButton(R.string.yes, new AbortSessionListener());
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void dial(Recipient recipient) {
|
||||
if (recipient == null) {
|
||||
// XXX toast?
|
||||
return;
|
||||
}
|
||||
|
||||
Intent dialIntent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + recipient.getNumber()));
|
||||
startActivity(dialIntent);
|
||||
}
|
||||
|
||||
private void displayMessageDetails(MessageRecord messageRecord) {
|
||||
String sender = messageRecord.getRecipients().getPrimaryRecipient().getNumber();
|
||||
String transport = messageRecord.isMms() ? "mms" : "sms";
|
||||
long date = messageRecord.getDate();
|
||||
|
||||
SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE MMM d, yyyy 'at' hh:mm:ss a zzz");
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Message Details");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setCancelable(false);
|
||||
builder.setMessage("Sender: " + sender + "\nTransport: " + transport.toUpperCase() + "\nSent/Received: " + dateFormatter.format(new Date(date)));
|
||||
builder.setPositiveButton("Ok", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void forwardMessage(MessageRecord messageRecord) {
|
||||
Intent composeIntent = new Intent(this, ComposeMessageActivity.class);
|
||||
composeIntent.putExtra("forwarded_message", messageRecord.getBody());
|
||||
composeIntent.putExtra("master_secret", masterSecret);
|
||||
startActivity(composeIntent);
|
||||
}
|
||||
|
||||
private void redecryptMessage(MessageRecord messageRecord, String address, String body) {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(this).markAsDecrypting(messageRecord.getId());
|
||||
DecryptingQueue.scheduleDecryption(this, masterSecret, messageRecord.getId(), address, body);
|
||||
}
|
||||
|
||||
private void copyMessageToClipboard(MessageRecord messageRecord) {
|
||||
String body = messageRecord.getBody();
|
||||
if (body == null) return;
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
|
||||
clipboard.setText(body);
|
||||
}
|
||||
|
||||
private void deleteMessage(MessageRecord messageRecord) {
|
||||
long messageId = messageRecord.getId();
|
||||
String transport = messageRecord.isMms() ? "mms" : "sms";
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Delete Message Confirmation");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage("Are you sure that you want to permanently delete this message?");
|
||||
builder.setPositiveButton(R.string.yes, new DeleteMessageListener(messageId, transport));
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void deleteThread() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Delete Thread Confirmation");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage("Are you sure that you want to permanently delete this conversation?");
|
||||
builder.setPositiveButton(R.string.yes, new DeleteThreadListener());
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void addAttachment() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setIcon(R.drawable.ic_dialog_attach);
|
||||
builder.setTitle("Add attachment");
|
||||
builder.setAdapter(attachmentAdapter, new AttachmentTypeListener());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void addAttachment(int type) {
|
||||
Log.w("ComposeMessageActivity", "Selected: " + type);
|
||||
switch (type) {
|
||||
case AttachmentTypeSelectorAdapter.ADD_IMAGE:
|
||||
AttachmentManager.selectImage(this, PICK_IMAGE); break;
|
||||
case AttachmentTypeSelectorAdapter.ADD_VIDEO:
|
||||
AttachmentManager.selectVideo(this, PICK_VIDEO); break;
|
||||
case AttachmentTypeSelectorAdapter.ADD_SOUND:
|
||||
AttachmentManager.selectAudio(this, PICK_AUDIO); break;
|
||||
}
|
||||
}
|
||||
|
||||
private void addAttachmentImage(Uri imageUri) {
|
||||
try {
|
||||
attachmentManager.setImage(imageUri);
|
||||
} catch (IOException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, there was an error setting your attachment.", Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAttachmentVideo(Uri videoUri) {
|
||||
try {
|
||||
attachmentManager.setVideo(videoUri);
|
||||
} catch (IOException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, there was an error setting your attachment.", Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
} catch (MediaTooLargeException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, the selected video exceeds message size restrictions.", Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAttachmentAudio(Uri audioUri) {
|
||||
try {
|
||||
attachmentManager.setAudio(audioUri);
|
||||
} catch (IOException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, there was an error setting your attachment.", Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
} catch (MediaTooLargeException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, the selected audio exceeds message size restrictions.", Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeReceivers() {
|
||||
killActivityReceiver = new KillActivityReceiver();
|
||||
securityUpdateReceiver = new SecurityUpdateReceiver();
|
||||
|
||||
registerReceiver(killActivityReceiver, new IntentFilter(KeyCachingService.PASSPHRASE_EXPIRED_EVENT), KeyCachingService.KEY_PERMISSION, null);
|
||||
registerReceiver(securityUpdateReceiver, new IntentFilter(KeyExchangeProcessor.SECURITY_UPDATE_EVENT), KeyCachingService.KEY_PERMISSION, null);
|
||||
}
|
||||
|
||||
private void registerPassphraseActivityStarted() {
|
||||
Intent intent = new Intent(this, KeyCachingService.class);
|
||||
intent.setAction(KeyCachingService.ACTIVITY_START_EVENT);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private void registerPassphraseActivityStopped() {
|
||||
Intent intent = new Intent(this, KeyCachingService.class);
|
||||
intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private void initializeSecurity(Recipients recipients) {
|
||||
if (recipients != null && recipients.isSingleRecipient() && KeyUtil.isSessionFor(this, recipients.getPrimaryRecipient())) {
|
||||
sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_lock_small, 0);
|
||||
sendButton.setCompoundDrawablePadding(10);
|
||||
this.sendEncrypted = true;
|
||||
this.characterCalculator = new EncryptedCharacterCalculator();
|
||||
} else {
|
||||
sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
this.sendEncrypted = false;
|
||||
this.characterCalculator = new CharacterCalculator();
|
||||
}
|
||||
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
recipientsPanel = (RecipientsPanel)findViewById(R.id.recipients);
|
||||
conversationView = (ListView)findViewById(R.id.conversation);
|
||||
recipients = getIntent().getParcelableExtra("recipients");
|
||||
threadId = getIntent().getLongExtra("thread_id", -1);
|
||||
addContactButton = (ImageButton)findViewById(R.id.contacts_button);
|
||||
sendButton = (Button)findViewById(R.id.send_button);
|
||||
composeText = (EditText)findViewById(R.id.embedded_text_editor);
|
||||
masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
|
||||
charactersLeft = (TextView)findViewById(R.id.space_left);
|
||||
titleBar = (TextView)findViewById(R.id.title_bar);
|
||||
greyLock = findViewById(R.id.secure_indicator);
|
||||
redLock = findViewById(R.id.secure_indicator_red);
|
||||
|
||||
attachmentAdapter = new AttachmentTypeSelectorAdapter(this);
|
||||
attachmentManager = new AttachmentManager(this);
|
||||
|
||||
AuthenticityCheckListener authenticityListener = new AuthenticityCheckListener();
|
||||
SendButtonListener sendButtonListener = new SendButtonListener();
|
||||
|
||||
recipientsPanel.setPanelChangeListener(new RecipientsPanelChangeListener());
|
||||
sendButton.setOnClickListener(sendButtonListener);
|
||||
addContactButton.setOnClickListener(new AddRecipientButtonListener());
|
||||
composeText.setOnKeyListener(new ComposeKeyPressedListener());
|
||||
composeText.addTextChangedListener(new OnTextChangedListener());
|
||||
composeText.setOnEditorActionListener(sendButtonListener);
|
||||
greyLock.setOnClickListener(authenticityListener);
|
||||
redLock.setOnClickListener(authenticityListener);
|
||||
this.registerForContextMenu(conversationView);
|
||||
this.registerForContextMenu(sendButton);
|
||||
|
||||
if (getIntent().getStringExtra("forwarded_message") != null)
|
||||
composeText.setText("FWD: " + getIntent().getStringExtra("forwarded_message"));
|
||||
}
|
||||
|
||||
private void initializeTitleBarSecurity() {
|
||||
redLock.setVisibility(View.GONE);
|
||||
greyLock.setVisibility(View.GONE);
|
||||
|
||||
if (recipients != null && recipients.isSingleRecipient() && KeyUtil.isSessionFor(this, recipients.getPrimaryRecipient())) {
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
AuthenticityCalculator.setAuthenticityStatus(this, recipient, masterSecret, greyLock, redLock, titleBar);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeTitleBar() {
|
||||
if (recipients != null && recipients.isSingleRecipient()) {
|
||||
String name = recipients.getPrimaryRecipient().getName();
|
||||
|
||||
if (name == null || name.trim().length() == 0)
|
||||
name = recipients.getPrimaryRecipient().getNumber();
|
||||
|
||||
titleBar.setText(name);
|
||||
} else {
|
||||
titleBar.setText("Compose Message");
|
||||
}
|
||||
|
||||
initializeTitleBarSecurity();
|
||||
}
|
||||
|
||||
private void initializeColors() {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(ApplicationPreferencesActivity.DARK_CONVERSATION_PREF, false)) {
|
||||
((LinearLayout)findViewById(R.id.layout_container)).setBackgroundDrawable(getResources().getDrawable(R.drawable.softgrey_background));
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateCharactersRemaining() {
|
||||
int charactersSpent = composeText.getText().length();
|
||||
CharacterCalculator.CharacterState characterState = characterCalculator.calculateCharacters(charactersSpent);
|
||||
charactersLeft.setText(characterState.charactersRemaining + "/" + characterState.maxMessageSize + " (" + characterState.messagesSpent + ")");
|
||||
}
|
||||
|
||||
private void initializeRecipientsInput() {
|
||||
recipientsPanel.setVisibility(View.VISIBLE);
|
||||
|
||||
if (this.recipients != null) {
|
||||
recipientsPanel.addRecipients(this.recipients);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeConversationAdapter() {
|
||||
Cursor cursor = DatabaseFactory.getMmsSmsDatabase(this).getConversation(threadId);
|
||||
conversationAdapter = new ConversationAdapter(recipients, threadId, this, cursor, masterSecret, new FailedIconClickHandler());
|
||||
conversationView.setAdapter(conversationAdapter);
|
||||
conversationView.setItemsCanFocus(true);
|
||||
conversationView.setVisibility(View.VISIBLE);
|
||||
|
||||
recipientsPanel.setVisibility(View.GONE);
|
||||
composeText.requestFocus();
|
||||
}
|
||||
|
||||
private boolean isExistingConversation() {
|
||||
return this.recipients != null && this.threadId != -1;
|
||||
}
|
||||
|
||||
private Recipients getRecipients() throws RecipientFormattingException {
|
||||
if (isExistingConversation()) return this.recipients;
|
||||
else return recipientsPanel.getRecipients();
|
||||
}
|
||||
|
||||
private String getMessage() throws InvalidMessageException {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String rawText = composeText.getText().toString();
|
||||
|
||||
if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent())
|
||||
throw new InvalidMessageException("Message is empty!");
|
||||
|
||||
if (!sendEncrypted && sp.getBoolean(ApplicationPreferencesActivity.WHITESPACE_PREF, true) && rawText.length() <= 145)
|
||||
rawText = rawText + " ";
|
||||
|
||||
return rawText;
|
||||
}
|
||||
|
||||
private void sendComplete(Recipients recipients, long threadId) {
|
||||
attachmentManager.clear();
|
||||
recipientsPanel.disable();
|
||||
composeText.setText("");
|
||||
|
||||
this.recipients = recipients;
|
||||
this.threadId = threadId;
|
||||
|
||||
if (this.conversationAdapter == null) {
|
||||
initializeConversationAdapter();
|
||||
this.recipientsPanel.setVisibility(View.GONE);
|
||||
initializeTitleBar();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(boolean sendEncrypted) {
|
||||
try {
|
||||
Recipients recipients = getRecipients();
|
||||
String message = getMessage();
|
||||
long allocatedThreadId;
|
||||
|
||||
if (attachmentManager.isAttachmentPresent()) {
|
||||
allocatedThreadId = MessageSender.sendMms(ComposeMessageActivity.this, masterSecret, recipients,
|
||||
threadId, attachmentManager.getSlideDeck(), message,
|
||||
sendEncrypted);
|
||||
} else if (recipients.isEmailRecipient()) {
|
||||
allocatedThreadId = MessageSender.sendMms(ComposeMessageActivity.this, masterSecret, recipients,
|
||||
threadId, new SlideDeck(), message, sendEncrypted);
|
||||
} else {
|
||||
allocatedThreadId = MessageSender.send(ComposeMessageActivity.this, masterSecret, recipients,
|
||||
threadId, message, sendEncrypted);
|
||||
}
|
||||
|
||||
sendComplete(recipients, allocatedThreadId);
|
||||
MessageNotifier.updateNotification(ComposeMessageActivity.this, false);
|
||||
} catch (RecipientFormattingException ex) {
|
||||
Toast.makeText(ComposeMessageActivity.this, "Recipient is not a valid SMS or email address!", Toast.LENGTH_LONG).show();
|
||||
Log.w("compose", ex);
|
||||
} catch (InvalidMessageException ex) {
|
||||
Toast.makeText(ComposeMessageActivity.this, "Message is empty!", Toast.LENGTH_SHORT).show();
|
||||
Log.w("compose", ex);
|
||||
} catch (MmsException e) {
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Listeners
|
||||
|
||||
private class KillActivityReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private class SecurityUpdateReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getLongExtra("thread_id", -1) == -1)
|
||||
return;
|
||||
|
||||
if (intent.getLongExtra("thread_id", -1) == threadId) {
|
||||
initializeSecurity(recipients);
|
||||
initializeTitleBar();
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InitiateSecureSessionListener implements DialogInterface.OnClickListener {
|
||||
public void onClick(DialogInterface dialogInterface, int which) {
|
||||
KeyExchangeInitiator.initiate(ComposeMessageActivity.this, masterSecret, recipients.getPrimaryRecipient(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private class FailedIconClickHandler extends Handler {
|
||||
@Override
|
||||
public void handleMessage(android.os.Message message) {
|
||||
String failedMessageText = (String)message.obj;
|
||||
ComposeMessageActivity.this.composeText.setText(failedMessageText);
|
||||
}
|
||||
}
|
||||
|
||||
private class OnTextChangedListener implements TextWatcher {
|
||||
public void afterTextChanged(Editable s) {
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,int after) {}
|
||||
public void onTextChanged(CharSequence s, int start, int before,int count) {}
|
||||
|
||||
}
|
||||
|
||||
private class ComposeKeyPressedListener implements OnKeyListener {
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(ComposeMessageActivity.this).getBoolean("pref_enter_sends", false)) {
|
||||
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
|
||||
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class AuthenticityCheckListener implements OnClickListener {
|
||||
public void onClick(View clicked) {
|
||||
String message = null;
|
||||
|
||||
if (clicked == greyLock) message = "This session is verified to be authentic.";
|
||||
else if (clicked == redLock) message = "Warning, this session has not yet been verified to be authentic. You should verify your session or the identity key of the person you are communicating with.";
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ComposeMessageActivity.this);
|
||||
builder.setTitle("Authenticity");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setCancelable(false);
|
||||
builder.setMessage(message);
|
||||
builder.setPositiveButton("Ok", null);
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteThreadListener implements DialogInterface.OnClickListener {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (threadId > 0) {
|
||||
DatabaseFactory.getThreadDatabase(ComposeMessageActivity.this).deleteConversation(threadId);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private class DeleteMessageListener implements DialogInterface.OnClickListener {
|
||||
private final long messageId;
|
||||
private final String transport;
|
||||
|
||||
public DeleteMessageListener(long messageId, String transport) {
|
||||
this.messageId = messageId;
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Log.w("ComposeMessageActivity", "Calling delete on: " + messageId);
|
||||
if (transport.equals("mms"))
|
||||
DatabaseFactory.getMmsDatabase(ComposeMessageActivity.this).delete(messageId);
|
||||
else
|
||||
DatabaseFactory.getSmsDatabase(ComposeMessageActivity.this).deleteMessage(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
private class AbortSessionListener implements DialogInterface.OnClickListener {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (recipients != null && recipients.isSingleRecipient()) {
|
||||
KeyUtil.abortSessionFor(ComposeMessageActivity.this, recipients.getPrimaryRecipient());
|
||||
initializeSecurity(recipients);
|
||||
initializeTitleBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AddRecipientButtonListener implements OnClickListener {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(ComposeMessageActivity.this, ContactSelectionActivity.class);
|
||||
startActivityForResult(intent, PICK_CONTACT);
|
||||
}
|
||||
};
|
||||
|
||||
private class AttachmentTypeListener implements DialogInterface.OnClickListener {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
addAttachment(attachmentAdapter.buttonToCommand(which));
|
||||
}
|
||||
}
|
||||
|
||||
private class RecipientsPanelChangeListener implements RecipientsPanel.RecipientsPanelChangedListener {
|
||||
public void onRecipientsPanelUpdate(Recipients recipients) {
|
||||
initializeSecurity(recipients);
|
||||
}
|
||||
}
|
||||
|
||||
private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener {
|
||||
public void onClick(View v) {
|
||||
sendMessage(sendEncrypted);
|
||||
}
|
||||
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
sendButton.performClick();
|
||||
composeText.clearFocus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
664
src/org/thoughtcrime/securesms/ConversationActivity.java
Normal file
@ -0,0 +1,664 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import org.thoughtcrime.securesms.components.RecipientsPanel;
|
||||
import org.thoughtcrime.securesms.crypto.AuthenticityCalculator;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.thoughtcrime.securesms.crypto.KeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentTypeSelectorAdapter;
|
||||
import org.thoughtcrime.securesms.mms.MediaTooLargeException;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.service.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.CharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.EncryptedCharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.InvalidMessageException;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Activity for displaying a message thread, as well as
|
||||
* composing/sending a new message into that thread.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class ConversationActivity extends SherlockFragmentActivity {
|
||||
|
||||
private static final int PICK_CONTACT = 1;
|
||||
private static final int PICK_IMAGE = 2;
|
||||
private static final int PICK_VIDEO = 3;
|
||||
private static final int PICK_AUDIO = 4;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private RecipientsPanel recipientsPanel;
|
||||
private EditText composeText;
|
||||
private ImageButton addContactButton;
|
||||
private Button sendButton;
|
||||
private TextView charactersLeft;
|
||||
|
||||
private AttachmentTypeSelectorAdapter attachmentAdapter;
|
||||
private AttachmentManager attachmentManager;
|
||||
private BroadcastReceiver killActivityReceiver;
|
||||
private BroadcastReceiver securityUpdateReceiver;
|
||||
|
||||
private Recipients recipients;
|
||||
private long threadId;
|
||||
private boolean sendEncrypted;
|
||||
|
||||
private CharacterCalculator characterCalculator = new CharacterCalculator();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(R.layout.conversation_activity);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
initializeReceivers();
|
||||
initializeResources();
|
||||
initializeTitleBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
initializeSecurity(recipients);
|
||||
initializeTitleBar();
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
if (!isExistingConversation())
|
||||
initializeRecipientsInput();
|
||||
|
||||
registerPassphraseActivityStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
registerPassphraseActivityStopped();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
unregisterReceiver(killActivityReceiver);
|
||||
unregisterReceiver(securityUpdateReceiver);
|
||||
MemoryCleaner.clean(masterSecret);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int reqCode, int resultCode, Intent data) {
|
||||
Log.w("ComposeMessageActivity", "onActivityResult called: " + resultCode + " , " + data);
|
||||
super.onActivityResult(reqCode, resultCode, data);
|
||||
|
||||
if (data == null || resultCode != Activity.RESULT_OK)
|
||||
return;
|
||||
|
||||
switch (reqCode) {
|
||||
case PICK_CONTACT:
|
||||
Recipients recipients = (Recipients)data.getParcelableExtra("recipients");
|
||||
|
||||
if (recipients != null)
|
||||
recipientsPanel.addRecipients(recipients);
|
||||
|
||||
break;
|
||||
case PICK_IMAGE:
|
||||
addAttachmentImage(data.getData());
|
||||
break;
|
||||
case PICK_VIDEO:
|
||||
addAttachmentVideo(data.getData());
|
||||
break;
|
||||
case PICK_AUDIO:
|
||||
addAttachmentAudio(data.getData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = this.getSupportMenuInflater();
|
||||
menu.clear();
|
||||
|
||||
if (isSingleExistingConversation() && sendEncrypted)
|
||||
{
|
||||
if (isAuthenticatedSession()) {
|
||||
inflater.inflate(R.menu.conversation_secure_verified, menu);
|
||||
} else {
|
||||
inflater.inflate(R.menu.conversation_secure_unverified, menu);
|
||||
}
|
||||
} else if (isSingleExistingConversation()) {
|
||||
inflater.inflate(R.menu.conversation_insecure, menu);
|
||||
}
|
||||
|
||||
inflater.inflate(R.menu.conversation, menu);
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_call: handleDial(recipients.getPrimaryRecipient()); return true;
|
||||
case R.id.menu_delete_thread: handleDeleteThread(); return true;
|
||||
case R.id.menu_add_attachment: handleAddAttachment(); return true;
|
||||
case R.id.menu_start_secure_session: handleStartSecureSession(); return true;
|
||||
case R.id.menu_abort_session: handleAbortSecureSession(); return true;
|
||||
case R.id.menu_verify_recipient: handleVerifyRecipient(); return true;
|
||||
case R.id.menu_verify_session: handleVerifySession(); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
if (sendEncrypted) {
|
||||
android.view.MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.conversation_button_context, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(android.view.MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_context_send_unencrypted: sendMessage(false); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//////// Event Handlers
|
||||
|
||||
private void handleVerifyRecipient() {
|
||||
Intent verifyIdentityIntent = new Intent(this, VerifyIdentityActivity.class);
|
||||
verifyIdentityIntent.putExtra("recipient", recipients.getPrimaryRecipient());
|
||||
verifyIdentityIntent.putExtra("master_secret", masterSecret);
|
||||
startActivity(verifyIdentityIntent);
|
||||
}
|
||||
|
||||
private void handleVerifySession() {
|
||||
Intent verifyKeysIntent = new Intent(this, VerifyKeysActivity.class);
|
||||
verifyKeysIntent.putExtra("recipient", recipients.getPrimaryRecipient());
|
||||
verifyKeysIntent.putExtra("master_secret", masterSecret);
|
||||
startActivity(verifyKeysIntent);
|
||||
}
|
||||
|
||||
private void handleStartSecureSession() {
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
String recipientName = (recipient.getName() == null ? recipient.getNumber() : recipient.getName());
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Initiate Secure Session?");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage("Initiate secure session with " + recipientName + "?");
|
||||
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
KeyExchangeInitiator.initiate(ConversationActivity.this, masterSecret,
|
||||
recipients.getPrimaryRecipient(), true);
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleAbortSecureSession() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Abort Secure Session Confirmation");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage("Are you sure that you want to abort this secure session?");
|
||||
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (recipients != null && recipients.isSingleRecipient()) {
|
||||
KeyUtil.abortSessionFor(ConversationActivity.this, recipients.getPrimaryRecipient());
|
||||
initializeSecurity(recipients);
|
||||
initializeTitleBar();
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleDial(Recipient recipient) {
|
||||
if (recipient == null) return;
|
||||
|
||||
Intent dialIntent = new Intent(Intent.ACTION_DIAL,
|
||||
Uri.parse("tel:" + recipient.getNumber()));
|
||||
startActivity(dialIntent);
|
||||
}
|
||||
|
||||
private void handleDeleteThread() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Delete Thread Confirmation");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage("Are you sure that you want to permanently delete this conversation?");
|
||||
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (threadId > 0) {
|
||||
DatabaseFactory.getThreadDatabase(ConversationActivity.this).deleteConversation(threadId);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleAddAttachment() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setIcon(R.drawable.ic_dialog_attach);
|
||||
builder.setTitle("Add attachment");
|
||||
builder.setAdapter(attachmentAdapter, new AttachmentTypeListener());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
///// Initializers
|
||||
|
||||
private void initializeTitleBar() {
|
||||
String title = null;
|
||||
String subtitle = null;
|
||||
|
||||
if (isSingleExistingConversation()) {
|
||||
|
||||
if (sendEncrypted) {
|
||||
title = AuthenticityCalculator.getAuthenticatedName(this,
|
||||
recipients.getPrimaryRecipient(),
|
||||
masterSecret);
|
||||
}
|
||||
|
||||
if (title == null || title.trim().length() == 0) {
|
||||
title = recipients.getPrimaryRecipient().getName();
|
||||
}
|
||||
|
||||
if (title == null || title.trim().length() == 0) {
|
||||
title = recipients.getPrimaryRecipient().getNumber();
|
||||
} else {
|
||||
subtitle = recipients.getPrimaryRecipient().getNumber();
|
||||
}
|
||||
} else {
|
||||
title = "Compose Message";
|
||||
}
|
||||
|
||||
this.getSupportActionBar().setTitle(title);
|
||||
|
||||
if (subtitle != null)
|
||||
this.getSupportActionBar().setSubtitle(PhoneNumberUtils.formatNumber(subtitle));
|
||||
|
||||
this.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void initializeSecurity(Recipients recipients) {
|
||||
if (isSingleExistingConversation() &&
|
||||
KeyUtil.isSessionFor(this, recipients.getPrimaryRecipient()))
|
||||
{
|
||||
sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_lock_small, 0);
|
||||
sendButton.setCompoundDrawablePadding(10);
|
||||
this.sendEncrypted = true;
|
||||
this.characterCalculator = new EncryptedCharacterCalculator();
|
||||
} else {
|
||||
sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
this.sendEncrypted = false;
|
||||
this.characterCalculator = new CharacterCalculator();
|
||||
}
|
||||
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
recipientsPanel = (RecipientsPanel)findViewById(R.id.recipients);
|
||||
recipients = getIntent().getParcelableExtra("recipients");
|
||||
threadId = getIntent().getLongExtra("thread_id", -1);
|
||||
addContactButton = (ImageButton)findViewById(R.id.contacts_button);
|
||||
sendButton = (Button)findViewById(R.id.send_button);
|
||||
composeText = (EditText)findViewById(R.id.embedded_text_editor);
|
||||
masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
|
||||
charactersLeft = (TextView)findViewById(R.id.space_left);
|
||||
|
||||
attachmentAdapter = new AttachmentTypeSelectorAdapter(this);
|
||||
attachmentManager = new AttachmentManager(this);
|
||||
|
||||
SendButtonListener sendButtonListener = new SendButtonListener();
|
||||
|
||||
recipientsPanel.setPanelChangeListener(new RecipientsPanelChangeListener());
|
||||
sendButton.setOnClickListener(sendButtonListener);
|
||||
addContactButton.setOnClickListener(new AddRecipientButtonListener());
|
||||
composeText.setOnKeyListener(new ComposeKeyPressedListener());
|
||||
composeText.addTextChangedListener(new OnTextChangedListener());
|
||||
composeText.setOnEditorActionListener(sendButtonListener);
|
||||
|
||||
registerForContextMenu(sendButton);
|
||||
|
||||
if (getIntent().getStringExtra("forwarded_message") != null)
|
||||
composeText.setText("FWD: " + getIntent().getStringExtra("forwarded_message"));
|
||||
}
|
||||
|
||||
private void initializeRecipientsInput() {
|
||||
recipientsPanel.setVisibility(View.VISIBLE);
|
||||
|
||||
if (this.recipients != null) {
|
||||
recipientsPanel.addRecipients(this.recipients);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initializeReceivers() {
|
||||
killActivityReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
securityUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getLongExtra("thread_id", -1) == -1)
|
||||
return;
|
||||
|
||||
if (intent.getLongExtra("thread_id", -1) == threadId) {
|
||||
initializeSecurity(recipients);
|
||||
initializeTitleBar();
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
registerReceiver(killActivityReceiver,
|
||||
new IntentFilter(KeyCachingService.PASSPHRASE_EXPIRED_EVENT),
|
||||
KeyCachingService.KEY_PERMISSION, null);
|
||||
|
||||
registerReceiver(securityUpdateReceiver,
|
||||
new IntentFilter(KeyExchangeProcessor.SECURITY_UPDATE_EVENT),
|
||||
KeyCachingService.KEY_PERMISSION, null);
|
||||
}
|
||||
|
||||
|
||||
//////// Helper Methods
|
||||
|
||||
private void addAttachment(int type) {
|
||||
Log.w("ComposeMessageActivity", "Selected: " + type);
|
||||
switch (type) {
|
||||
case AttachmentTypeSelectorAdapter.ADD_IMAGE:
|
||||
AttachmentManager.selectImage(this, PICK_IMAGE); break;
|
||||
case AttachmentTypeSelectorAdapter.ADD_VIDEO:
|
||||
AttachmentManager.selectVideo(this, PICK_VIDEO); break;
|
||||
case AttachmentTypeSelectorAdapter.ADD_SOUND:
|
||||
AttachmentManager.selectAudio(this, PICK_AUDIO); break;
|
||||
}
|
||||
}
|
||||
|
||||
private void addAttachmentImage(Uri imageUri) {
|
||||
try {
|
||||
attachmentManager.setImage(imageUri);
|
||||
} catch (IOException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, there was an error setting your attachment.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAttachmentVideo(Uri videoUri) {
|
||||
try {
|
||||
attachmentManager.setVideo(videoUri);
|
||||
} catch (IOException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, there was an error setting your attachment.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
} catch (MediaTooLargeException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, the selected video exceeds message size restrictions.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAttachmentAudio(Uri audioUri) {
|
||||
try {
|
||||
attachmentManager.setAudio(audioUri);
|
||||
} catch (IOException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, there was an error setting your attachment.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
} catch (MediaTooLargeException e) {
|
||||
attachmentManager.clear();
|
||||
Toast.makeText(this, "Sorry, the selected audio exceeds message size restrictions.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateCharactersRemaining() {
|
||||
int charactersSpent = composeText.getText().length();
|
||||
CharacterCalculator.CharacterState characterState = characterCalculator.calculateCharacters(charactersSpent);
|
||||
charactersLeft.setText(characterState.charactersRemaining + "/" + characterState.maxMessageSize + " (" + characterState.messagesSpent + ")");
|
||||
}
|
||||
|
||||
private boolean isExistingConversation() {
|
||||
return this.recipients != null && this.threadId != -1;
|
||||
}
|
||||
|
||||
private boolean isSingleExistingConversation() {
|
||||
return this.recipients != null && this.recipients.isSingleRecipient();
|
||||
}
|
||||
|
||||
private boolean isAuthenticatedSession() {
|
||||
return AuthenticityCalculator.isAuthenticated(this,
|
||||
recipients.getPrimaryRecipient(),
|
||||
masterSecret);
|
||||
}
|
||||
|
||||
private Recipients getRecipients() throws RecipientFormattingException {
|
||||
if (isExistingConversation()) return this.recipients;
|
||||
else return recipientsPanel.getRecipients();
|
||||
}
|
||||
|
||||
private String getMessage() throws InvalidMessageException {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String rawText = composeText.getText().toString();
|
||||
|
||||
if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent())
|
||||
throw new InvalidMessageException("Message is empty!");
|
||||
|
||||
if (!sendEncrypted && sp.getBoolean(ApplicationPreferencesActivity.WHITESPACE_PREF, true) && rawText.length() <= 145)
|
||||
rawText = rawText + " ";
|
||||
|
||||
return rawText;
|
||||
}
|
||||
|
||||
private void sendComplete(Recipients recipients, long threadId) {
|
||||
attachmentManager.clear();
|
||||
recipientsPanel.disable();
|
||||
composeText.setText("");
|
||||
|
||||
this.recipients = recipients;
|
||||
this.threadId = threadId;
|
||||
|
||||
if (this.recipientsPanel.getVisibility() == View.VISIBLE) {
|
||||
///XXX call down to fragment! ??
|
||||
|
||||
// initializeConversationAdapter();
|
||||
this.recipientsPanel.setVisibility(View.GONE);
|
||||
initializeTitleBar();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(boolean sendEncrypted) {
|
||||
try {
|
||||
Recipients recipients = getRecipients();
|
||||
String message = getMessage();
|
||||
long allocatedThreadId;
|
||||
|
||||
if (attachmentManager.isAttachmentPresent()) {
|
||||
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
|
||||
threadId, attachmentManager.getSlideDeck(), message,
|
||||
sendEncrypted);
|
||||
} else if (recipients.isEmailRecipient()) {
|
||||
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
|
||||
threadId, new SlideDeck(), message, sendEncrypted);
|
||||
} else {
|
||||
allocatedThreadId = MessageSender.send(ConversationActivity.this, masterSecret, recipients,
|
||||
threadId, message, sendEncrypted);
|
||||
}
|
||||
|
||||
sendComplete(recipients, allocatedThreadId);
|
||||
MessageNotifier.updateNotification(ConversationActivity.this, false);
|
||||
} catch (RecipientFormattingException ex) {
|
||||
Toast.makeText(ConversationActivity.this, "Recipient is not a valid SMS or email address!", Toast.LENGTH_LONG).show();
|
||||
Log.w("compose", ex);
|
||||
} catch (InvalidMessageException ex) {
|
||||
Toast.makeText(ConversationActivity.this, "Message is empty!", Toast.LENGTH_SHORT).show();
|
||||
Log.w("compose", ex);
|
||||
} catch (MmsException e) {
|
||||
Log.w("ComposeMessageActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPassphraseActivityStarted() {
|
||||
Intent intent = new Intent(this, KeyCachingService.class);
|
||||
intent.setAction(KeyCachingService.ACTIVITY_START_EVENT);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private void registerPassphraseActivityStopped() {
|
||||
Intent intent = new Intent(this, KeyCachingService.class);
|
||||
intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
// Listeners
|
||||
|
||||
private class AddRecipientButtonListener implements OnClickListener {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(ConversationActivity.this, ContactSelectionActivity.class);
|
||||
startActivityForResult(intent, PICK_CONTACT);
|
||||
}
|
||||
};
|
||||
|
||||
private class AttachmentTypeListener implements DialogInterface.OnClickListener {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
addAttachment(attachmentAdapter.buttonToCommand(which));
|
||||
}
|
||||
}
|
||||
|
||||
private class RecipientsPanelChangeListener implements RecipientsPanel.RecipientsPanelChangedListener {
|
||||
public void onRecipientsPanelUpdate(Recipients recipients) {
|
||||
initializeSecurity(recipients);
|
||||
}
|
||||
}
|
||||
|
||||
private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener {
|
||||
public void onClick(View v) {
|
||||
sendMessage(sendEncrypted);
|
||||
}
|
||||
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
sendButton.performClick();
|
||||
composeText.clearFocus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private class OnTextChangedListener implements TextWatcher {
|
||||
public void afterTextChanged(Editable s) {
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,int after) {}
|
||||
public void onTextChanged(CharSequence s, int start, int before,int count) {}
|
||||
|
||||
}
|
||||
|
||||
private class ComposeKeyPressedListener implements OnKeyListener {
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(ConversationActivity.this).getBoolean("pref_enter_sends", false)) {
|
||||
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
|
||||
sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@ -10,13 +10,20 @@
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
@ -33,40 +40,34 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.MessageNotifier;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* A cursor adapter for a conversation thread. Ultimately
|
||||
* used by ComposeMessageActivity to display a conversation
|
||||
* thread in a ListActivity.
|
||||
*
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class ConversationAdapter extends CursorAdapter {
|
||||
|
||||
|
||||
private static final int MAX_CACHE_SIZE = 40;
|
||||
|
||||
private final TouchListener touchListener = new TouchListener();
|
||||
private final LinkedHashMap<String,MessageRecord> messageRecordCache;
|
||||
private final Handler failedIconClickHandler;
|
||||
private final long threadId;
|
||||
private final long threadId;
|
||||
private final Context context;
|
||||
private final Recipients recipients;
|
||||
private final MasterSecret masterSecret;
|
||||
private final MasterCipher masterCipher;
|
||||
|
||||
|
||||
private boolean dataChanged;
|
||||
|
||||
public ConversationAdapter(Recipients recipients, long threadId, Context context, Cursor c, MasterSecret masterSecret, Handler failedIconClickHandler) {
|
||||
super(context, c);
|
||||
|
||||
public ConversationAdapter(Recipients recipients, long threadId, Context context, MasterSecret masterSecret, Handler failedIconClickHandler) {
|
||||
super(context, null);
|
||||
this.context = context;
|
||||
this.recipients = recipients;
|
||||
this.threadId = threadId;
|
||||
@ -75,14 +76,14 @@ public class ConversationAdapter extends CursorAdapter {
|
||||
this.dataChanged = false;
|
||||
this.failedIconClickHandler = failedIconClickHandler;
|
||||
this.messageRecordCache = initializeCache();
|
||||
|
||||
|
||||
DatabaseFactory.getThreadDatabase(context).setRead(threadId);
|
||||
MessageNotifier.updateNotification(context, false);
|
||||
}
|
||||
|
||||
|
||||
private Recipient buildRecipient(String address) {
|
||||
Recipient recipient;
|
||||
|
||||
|
||||
try {
|
||||
if (address == null) recipient = recipients.getPrimaryRecipient();
|
||||
else recipient = RecipientFactory.getRecipientsFromString(context, address).getPrimaryRecipient();
|
||||
@ -90,17 +91,17 @@ public class ConversationAdapter extends CursorAdapter {
|
||||
Log.w("ConversationAdapter", e);
|
||||
recipient = new Recipient("Unknown", "Unknown", null);
|
||||
}
|
||||
|
||||
|
||||
return recipient;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
|
||||
String type = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.TRANSPORT));
|
||||
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
|
||||
|
||||
((ConversationItem)view).set(masterSecret, messageRecord, failedIconClickHandler);
|
||||
|
||||
((ConversationItem)view).set(masterSecret, messageRecord, failedIconClickHandler);
|
||||
view.setOnTouchListener(touchListener);
|
||||
}
|
||||
|
||||
@ -111,12 +112,12 @@ public class ConversationAdapter extends CursorAdapter {
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
private MessageRecord getNewMmsMessageRecord(long messageId, Cursor cursor) {
|
||||
MessageRecord messageRecord = getNewSmsMessageRecord(messageId, cursor);
|
||||
long mmsType = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_TYPE));
|
||||
long mmsBox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
|
||||
|
||||
|
||||
try {
|
||||
return MmsFactory.getMms(context, masterSecret, messageRecord, mmsType, mmsBox);
|
||||
} catch (MmsException me) {
|
||||
@ -124,64 +125,65 @@ public class ConversationAdapter extends CursorAdapter {
|
||||
return messageRecord;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private MessageRecord getNewSmsMessageRecord(long messageId, Cursor cursor) {
|
||||
long date = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE));
|
||||
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
|
||||
Recipient recipient = buildRecipient(address);
|
||||
MessageRecord messageRecord = new MessageRecord(messageId, recipients, date, type, threadId);
|
||||
|
||||
|
||||
messageRecord.setMessageRecipient(recipient);
|
||||
setBody(cursor, messageRecord);
|
||||
|
||||
|
||||
return messageRecord;
|
||||
}
|
||||
|
||||
|
||||
private MessageRecord getMessageRecord(long messageId, Cursor cursor, String type) {
|
||||
if (messageRecordCache.containsKey(type + messageId))
|
||||
return messageRecordCache.get(type + messageId);
|
||||
|
||||
|
||||
MessageRecord messageRecord;
|
||||
|
||||
|
||||
if (type.equals("mms")) messageRecord = getNewMmsMessageRecord(messageId, cursor);
|
||||
else messageRecord = getNewSmsMessageRecord(messageId, cursor);
|
||||
|
||||
messageRecordCache.put(type + messageId, messageRecord);
|
||||
|
||||
messageRecordCache.put(type + messageId, messageRecord);
|
||||
return messageRecord;
|
||||
}
|
||||
|
||||
|
||||
protected void setBody(Cursor cursor, MessageRecord message) {
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
||||
|
||||
|
||||
if (body == null)
|
||||
message.setBody("");
|
||||
else
|
||||
MessageDisplayHelper.setDecryptedMessageBody(body, message, masterCipher);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onContentChanged() {
|
||||
super.onContentChanged();
|
||||
messageRecordCache.clear();
|
||||
DatabaseFactory.getThreadDatabase(context).setRead(threadId);
|
||||
this.dataChanged = true;
|
||||
}
|
||||
|
||||
|
||||
public void close() {
|
||||
this.getCursor().close();
|
||||
}
|
||||
|
||||
|
||||
private class TouchListener implements View.OnTouchListener {
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (ConversationAdapter.this.dataChanged) {
|
||||
ConversationAdapter.this.dataChanged = false;
|
||||
MessageNotifier.updateNotification(context, false);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private LinkedHashMap<String,MessageRecord> initializeCache() {
|
||||
return new LinkedHashMap<String,MessageRecord>() {
|
||||
@Override
|
||||
@ -190,6 +192,6 @@ public class ConversationAdapter extends CursorAdapter {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
176
src/org/thoughtcrime/securesms/ConversationFragment.java
Normal file
@ -0,0 +1,176 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.ClipboardManager;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
public class ConversationFragment extends SherlockListFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>
|
||||
{
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private Recipients recipients;
|
||||
private long threadId;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
return inflater.inflate(R.layout.conversation_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle bundle) {
|
||||
super.onActivityCreated(bundle);
|
||||
|
||||
initializeResources();
|
||||
initializeListAdapter();
|
||||
registerForContextMenu(getListView());
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
android.view.MenuInflater inflater = this.getSherlockActivity().getMenuInflater();
|
||||
menu.clear();
|
||||
|
||||
inflater.inflate(R.menu.conversation_context, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(android.view.MenuItem item) {
|
||||
Cursor cursor = ((CursorAdapter)getListAdapter()).getCursor();
|
||||
ConversationItem conversationItem = (ConversationItem)(((ConversationAdapter)getListAdapter()).newView(getActivity(), cursor, null));
|
||||
MessageRecord messageRecord = conversationItem.getMessageRecord();
|
||||
|
||||
switch(item.getItemId()) {
|
||||
case R.id.menu_context_copy: handleCopyMessage(messageRecord); return true;
|
||||
case R.id.menu_context_delete_message: handleDeleteMessage(messageRecord); return true;
|
||||
case R.id.menu_context_details: handleDisplayDetails(messageRecord); return true;
|
||||
case R.id.menu_context_forward: handleForwardMessage(messageRecord); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleCopyMessage(MessageRecord message) {
|
||||
String body = message.getBody();
|
||||
if (body == null) return;
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager)getActivity()
|
||||
.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(body);
|
||||
}
|
||||
|
||||
private void handleDeleteMessage(MessageRecord message) {
|
||||
final long messageId = message.getId();
|
||||
final String transport = message.isMms() ? "mms" : "sms";
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setTitle("Delete Message Confirmation");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage("Are you sure that you want to permanently delete this message?");
|
||||
|
||||
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (transport.equals("mms")) {
|
||||
DatabaseFactory.getMmsDatabase(getActivity()).delete(messageId);
|
||||
} else {
|
||||
DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleDisplayDetails(MessageRecord message) {
|
||||
String sender = message.getRecipients().getPrimaryRecipient().getNumber();
|
||||
String transport = message.isMms() ? "mms" : "sms";
|
||||
long date = message.getDate();
|
||||
|
||||
SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE MMM d, yyyy 'at' hh:mm:ss a zzz");
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setTitle("Message Details");
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setCancelable(false);
|
||||
builder.setMessage("Sender: " + sender + "\nTransport: " + transport.toUpperCase() +
|
||||
"\nSent/Received: " + dateFormatter.format(new Date(date)));
|
||||
builder.setPositiveButton("Ok", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleForwardMessage(MessageRecord message) {
|
||||
Intent composeIntent = new Intent(getActivity(), ConversationActivity.class);
|
||||
composeIntent.putExtra("forwarded_message", message.getBody());
|
||||
composeIntent.putExtra("master_secret", masterSecret);
|
||||
startActivity(composeIntent);
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.masterSecret = (MasterSecret)this.getActivity().getIntent()
|
||||
.getParcelableExtra("master_secret");
|
||||
this.recipients = this.getActivity().getIntent().getParcelableExtra("recipients");
|
||||
this.threadId = this.getActivity().getIntent().getLongExtra("thread_id", -1);
|
||||
}
|
||||
|
||||
private void initializeListAdapter() {
|
||||
if (this.recipients != null && this.threadId != -1) {
|
||||
this.setListAdapter(new ConversationAdapter(recipients, threadId, getActivity(), masterSecret, new FailedIconClickHandler()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
|
||||
if (this.threadId != -1) {
|
||||
return new ConversationLoader(getActivity(), threadId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
|
||||
((CursorAdapter)getListAdapter()).changeCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> arg0) {
|
||||
((CursorAdapter)getListAdapter()).changeCursor(null);
|
||||
}
|
||||
|
||||
private class FailedIconClickHandler extends Handler {
|
||||
@Override
|
||||
public void handleMessage(android.os.Message message) {
|
||||
assert(false);
|
||||
// String failedMessageText = (String)message.obj;
|
||||
// ConversationActivity.this.composeText.setText(failedMessageText);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -145,7 +145,7 @@ public class ConversationListActivity extends SherlockFragmentActivity
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, ComposeMessageActivity.class);
|
||||
Intent intent = new Intent(this, ConversationActivity.class);
|
||||
intent.putExtra("recipients", recipients);
|
||||
intent.putExtra("thread_id", threadId);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@ -10,42 +10,53 @@
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.SessionRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class AuthenticityCalculator {
|
||||
|
||||
private static void setAuthentictyForIdentity(Context context, MasterSecret masterSecret, IdentityKey identityKey, View grey, View red, TextView titleBar)
|
||||
private static boolean isAuthenticatedIdentity(Context context,
|
||||
MasterSecret masterSecret,
|
||||
IdentityKey identityKey)
|
||||
{
|
||||
String identityName = DatabaseFactory.getIdentityDatabase(context).getNameForIdentity(masterSecret, identityKey);
|
||||
|
||||
if (identityName == null) {
|
||||
red.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
grey.setVisibility(View.VISIBLE);
|
||||
titleBar.setText(identityName);
|
||||
String identityName = DatabaseFactory.getIdentityDatabase(context)
|
||||
.getNameForIdentity(masterSecret, identityKey);
|
||||
|
||||
if (identityName == null) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
public static void setAuthenticityStatus(Context context, Recipient recipient, MasterSecret masterSecret, View grey, View red, TextView titleBar)
|
||||
|
||||
public static String getAuthenticatedName(Context context,
|
||||
Recipient recipient,
|
||||
MasterSecret masterSecret)
|
||||
{
|
||||
SessionRecord session = new SessionRecord(context, masterSecret, recipient);
|
||||
|
||||
if (session.isVerifiedSession()) grey.setVisibility(View.VISIBLE);
|
||||
else if (session.getIdentityKey() != null) setAuthentictyForIdentity(context, masterSecret, session.getIdentityKey(), grey, red, titleBar);
|
||||
else red.setVisibility(View.VISIBLE);
|
||||
return DatabaseFactory.getIdentityDatabase(context)
|
||||
.getNameForIdentity(masterSecret, session.getIdentityKey());
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAuthenticated(Context context,
|
||||
Recipient recipient,
|
||||
MasterSecret masterSecret)
|
||||
{
|
||||
SessionRecord session = new SessionRecord(context, masterSecret, recipient);
|
||||
|
||||
if (session.isVerifiedSession()) {
|
||||
return true;
|
||||
} else if (session.getIdentityKey() != null) {
|
||||
return isAuthenticatedIdentity(context, masterSecret, session.getIdentityKey());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package org.thoughtcrime.securesms.database.loaders;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
|
||||
public class ConversationLoader extends CursorLoader {
|
||||
private final Context context;
|
||||
private final long threadId;
|
||||
|
||||
public ConversationLoader(Context context, long threadId) {
|
||||
super(context);
|
||||
this.context = context.getApplicationContext();
|
||||
this.threadId = threadId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
|
||||
}
|
||||
}
|