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=".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=".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=".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=".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>
|
<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms;
|
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.MasterCipher;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
@ -33,14 +40,8 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
|||||||
import org.thoughtcrime.securesms.service.MessageNotifier;
|
import org.thoughtcrime.securesms.service.MessageNotifier;
|
||||||
|
|
||||||
import ws.com.google.android.mms.MmsException;
|
import ws.com.google.android.mms.MmsException;
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import java.util.LinkedHashMap;
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.CursorAdapter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cursor adapter for a conversation thread. Ultimately
|
* A cursor adapter for a conversation thread. Ultimately
|
||||||
@ -65,8 +66,8 @@ public class ConversationAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
private boolean dataChanged;
|
private boolean dataChanged;
|
||||||
|
|
||||||
public ConversationAdapter(Recipients recipients, long threadId, Context context, Cursor c, MasterSecret masterSecret, Handler failedIconClickHandler) {
|
public ConversationAdapter(Recipients recipients, long threadId, Context context, MasterSecret masterSecret, Handler failedIconClickHandler) {
|
||||||
super(context, c);
|
super(context, null);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.recipients = recipients;
|
this.recipients = recipients;
|
||||||
this.threadId = threadId;
|
this.threadId = threadId;
|
||||||
@ -160,6 +161,7 @@ public class ConversationAdapter extends CursorAdapter {
|
|||||||
MessageDisplayHelper.setDecryptedMessageBody(body, message, masterCipher);
|
MessageDisplayHelper.setDecryptedMessageBody(body, message, masterCipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void onContentChanged() {
|
protected void onContentChanged() {
|
||||||
super.onContentChanged();
|
super.onContentChanged();
|
||||||
messageRecordCache.clear();
|
messageRecordCache.clear();
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(this, ComposeMessageActivity.class);
|
Intent intent = new Intent(this, ConversationActivity.class);
|
||||||
intent.putExtra("recipients", recipients);
|
intent.putExtra("recipients", recipients);
|
||||||
intent.putExtra("thread_id", threadId);
|
intent.putExtra("thread_id", threadId);
|
||||||
intent.putExtra("master_secret", masterSecret);
|
intent.putExtra("master_secret", masterSecret);
|
||||||
|
@ -16,36 +16,47 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.SessionRecord;
|
import org.thoughtcrime.securesms.database.SessionRecord;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
public class AuthenticityCalculator {
|
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);
|
String identityName = DatabaseFactory.getIdentityDatabase(context)
|
||||||
|
.getNameForIdentity(masterSecret, identityKey);
|
||||||
|
|
||||||
if (identityName == null) {
|
if (identityName == null) return false;
|
||||||
red.setVisibility(View.VISIBLE);
|
else return true;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
grey.setVisibility(View.VISIBLE);
|
|
||||||
titleBar.setText(identityName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
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);
|
SessionRecord session = new SessionRecord(context, masterSecret, recipient);
|
||||||
|
|
||||||
if (session.isVerifiedSession()) grey.setVisibility(View.VISIBLE);
|
if (session.isVerifiedSession()) {
|
||||||
else if (session.getIdentityKey() != null) setAuthentictyForIdentity(context, masterSecret, session.getIdentityKey(), grey, red, titleBar);
|
return true;
|
||||||
else red.setVisibility(View.VISIBLE);
|
} 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);
|
||||||
|
}
|
||||||
|
}
|