initial commit of v0.8.0
89
AndroidManifest.xml
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.thialfihar.android.apg"
|
||||||
|
android:versionCode="3" android:versionName="0.8.0">
|
||||||
|
<application android:icon="@drawable/icon" android:label="@string/app_name">
|
||||||
|
<activity android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".PublicKeyListActivity"
|
||||||
|
android:label="@string/title_managePublicKeys"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".SecretKeyListActivity"
|
||||||
|
android:label="@string/title_manageSecretKeys"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".EditKeyActivity"
|
||||||
|
android:label="@string/title_editKey"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".SelectPublicKeyListActivity"
|
||||||
|
android:label="@string/title_selectRecipients"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".SelectSecretKeyListActivity"
|
||||||
|
android:label="@string/title_selectSignature"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".EncryptMessageActivity"
|
||||||
|
android:label="@string/title_encryptMessage"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thialfihar.android.apg.intent.ENCRYPT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".DecryptMessageActivity"
|
||||||
|
android:label="@string/title_decryptMessage"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:mimeType="text/*"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.thialfihar.android.apg.intent.DECRYPT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".MailListActivity"
|
||||||
|
android:label="@string/title_mailInbox"
|
||||||
|
android:configChanges="keyboardHidden|orientation|keyboard">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider android:name="org.thialfihar.android.apg.provider.DataProvider"
|
||||||
|
android:authorities="org.thialfihar.android.apg.provider" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="5" />
|
||||||
|
|
||||||
|
<uses-permission android:name="com.google.android.providers.gmail.permission.READ_GMAIL" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
|
||||||
|
</manifest>
|
13
default.properties
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# This file is automatically generated by Android Tools.
|
||||||
|
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems.
|
||||||
|
#
|
||||||
|
# To customize properties used by the Ant build system use,
|
||||||
|
# "build.properties", and override values to adapt the script to your
|
||||||
|
# project structure.
|
||||||
|
|
||||||
|
# Indicates whether an apk should be generated for each density.
|
||||||
|
split.density=false
|
||||||
|
# Project target.
|
||||||
|
target=android-4
|
32
res/drawable-finger/btn_circle.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2009 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_window_focused="false" android:state_enabled="true"
|
||||||
|
android:drawable="@drawable/btn_circle_normal" />
|
||||||
|
<item android:state_window_focused="false" android:state_enabled="false"
|
||||||
|
android:drawable="@drawable/btn_circle_disable" />
|
||||||
|
<item android:state_pressed="true"
|
||||||
|
android:drawable="@drawable/btn_circle_pressed" />
|
||||||
|
<item android:state_focused="true" android:state_enabled="true"
|
||||||
|
android:drawable="@drawable/btn_circle_selected" />
|
||||||
|
<item android:state_enabled="true"
|
||||||
|
android:drawable="@drawable/btn_circle_normal" />
|
||||||
|
<item android:state_focused="true"
|
||||||
|
android:drawable="@drawable/btn_circle_disable_focused" />
|
||||||
|
<item
|
||||||
|
android:drawable="@drawable/btn_circle_disable" />
|
||||||
|
</selector>
|
BIN
res/drawable-hdpi-finger/btn_circle_disable.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res/drawable-hdpi-finger/btn_circle_disable_focused.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
res/drawable-hdpi-finger/btn_circle_normal.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
res/drawable-hdpi-finger/btn_circle_pressed.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res/drawable-hdpi-finger/btn_circle_selected.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
res/drawable-hdpi-finger/ic_btn_round_minus.png
Normal file
After Width: | Height: | Size: 536 B |
BIN
res/drawable-hdpi-finger/ic_btn_round_plus.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-hdpi/encrypted.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
res/drawable-hdpi/encrypted_large.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
res/drawable-hdpi/encrypted_small.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
res/drawable-hdpi/icon.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
res/drawable-hdpi/key.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
res/drawable-hdpi/key_large.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
res/drawable-hdpi/key_small.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
res/drawable-hdpi/overlay_error.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
res/drawable-hdpi/overlay_ok.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/drawable-hdpi/signed.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
res/drawable-hdpi/signed_large.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
res/drawable-hdpi/signed_small.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
res/drawable-ldpi/encrypted.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-ldpi/encrypted_large.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
res/drawable-ldpi/encrypted_small.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-ldpi/icon.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
res/drawable-ldpi/key.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-ldpi/key_large.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
res/drawable-ldpi/key_small.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
res/drawable-ldpi/overlay_error.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-ldpi/overlay_ok.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
res/drawable-ldpi/signed.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-ldpi/signed_large.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
res/drawable-ldpi/signed_small.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-mdpi-finger/btn_circle_disable.png
Normal file
After Width: | Height: | Size: 938 B |
BIN
res/drawable-mdpi-finger/btn_circle_disable_focused.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-mdpi-finger/btn_circle_normal.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-mdpi-finger/btn_circle_pressed.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-mdpi-finger/btn_circle_selected.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-mdpi-finger/ic_btn_round_minus.png
Normal file
After Width: | Height: | Size: 288 B |
BIN
res/drawable-mdpi-finger/ic_btn_round_plus.png
Normal file
After Width: | Height: | Size: 526 B |
BIN
res/drawable-mdpi/encrypted.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
res/drawable-mdpi/encrypted_large.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
res/drawable-mdpi/encrypted_small.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-mdpi/icon.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
res/drawable-mdpi/key.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
res/drawable-mdpi/key_large.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
res/drawable-mdpi/key_small.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-mdpi/overlay_error.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-mdpi/overlay_ok.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-mdpi/signed.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
res/drawable-mdpi/signed_large.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
res/drawable-mdpi/signed_small.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
35
res/layout/account_item.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:singleLine="true"
|
||||||
|
android:layout_marginRight="?android:attr/scrollbarSize"
|
||||||
|
android:paddingLeft="6dip"
|
||||||
|
android:paddingTop="6dip"
|
||||||
|
android:paddingBottom="6dip"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/account_name"
|
||||||
|
android:text="someone@gmail.com"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
52
res/layout/create_key.xml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_width="fill_parent">
|
||||||
|
<TableLayout
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:stretchColumns="1"
|
||||||
|
android:layout_marginRight="?android:attr/scrollbarSize"
|
||||||
|
android:paddingLeft="6dip">
|
||||||
|
<TableRow>
|
||||||
|
<TextView android:id="@+id/label_algorithm"
|
||||||
|
android:text="Algorithm"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/algorithm"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TextView android:id="@+id/label_size"
|
||||||
|
android:text="Key Size"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"/>
|
||||||
|
<EditText android:id="@+id/size"
|
||||||
|
android:text="1024"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent" android:gravity="right" android:numeric="integer"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
</ScrollView>
|
97
res/layout/decrypt_message.xml
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/message"
|
||||||
|
android:inputType="text|textCapSentences|textMultiLine|textLongMessage"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="top"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:paddingLeft="5dip"
|
||||||
|
android:paddingRight="5dip">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_signature"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_weight="2">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_signature"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/signed_large"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_signature_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/overlay_error"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:paddingLeft="5dip">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id"
|
||||||
|
android:text="Main User Id"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="left"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id_rest"
|
||||||
|
android:text="Main User Id Rest"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" android:layout_gravity="left"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_decrypt"
|
||||||
|
android:text="@string/btn_decrypt"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
58
res/layout/edit_key.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:paddingTop="5dip"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_weight="1">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginRight="?android:attr/scrollbarSize"/>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
style="@android:style/ButtonBar">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_save"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/btn_save"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_discard"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/btn_doNotSave"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
113
res/layout/edit_key_key_item.xml
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<org.thialfihar.android.apg.ui.widget.KeyEditor
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="5dip">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/separator"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="1dip"
|
||||||
|
android:background="?android:attr/listDivider"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<TableLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="16dip"
|
||||||
|
android:stretchColumns="1">
|
||||||
|
<TableRow>
|
||||||
|
<TextView android:id="@+id/label_key_id" android:text="Key ID"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_id"
|
||||||
|
android:text="00000000 00000000"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:paddingRight="5dip"
|
||||||
|
android:typeface="monospace"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TextView android:id="@+id/label_algorithm"
|
||||||
|
android:text="Algorithm"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"/>
|
||||||
|
<TextView android:id="@+id/algorithm"
|
||||||
|
android:text="Name"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:paddingRight="5dip"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TextView android:id="@+id/label_creation"
|
||||||
|
android:text="Creation"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/creation"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TextView android:id="@+id/label_expiry"
|
||||||
|
android:text="Expiry"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/expiry"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TextView android:id="@+id/label_usage"
|
||||||
|
android:text="Usage"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/usage"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/edit_delete"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/MinusButton"
|
||||||
|
android:layout_gravity="center_vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</org.thialfihar.android.apg.ui.widget.KeyEditor>
|
66
res/layout/edit_key_section.xml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<org.thialfihar.android.apg.ui.widget.SectionView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="1dip"
|
||||||
|
android:background="?android:attr/listDivider"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="14dip"
|
||||||
|
android:layout_marginTop="2dip"
|
||||||
|
android:layout_marginBottom="2dip"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:focusable="true"
|
||||||
|
android:clickable="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:text="Section Name"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:fadingEdge="horizontal"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
style="@style/PlusButton"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/editors"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="6dip"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</org.thialfihar.android.apg.ui.widget.SectionView>
|
96
res/layout/edit_key_user_id_item.xml
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<org.thialfihar.android.apg.ui.widget.UserIdEditor
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="5dip">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/separator"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="1dip"
|
||||||
|
android:background="?android:attr/listDivider"/>
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/is_main_user_id" android:text="Main User ID"
|
||||||
|
android:layout_height="wrap_content" android:layout_width="wrap_content"
|
||||||
|
android:layout_marginLeft="20dip"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<TableLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="16dip">
|
||||||
|
<TableRow>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name_label"
|
||||||
|
android:text="Name"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:paddingRight="5dip"/>
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/email_label"
|
||||||
|
android:text="Email"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:paddingRight="5dip"/>
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/email"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/comment_label"
|
||||||
|
android:text="Comment"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:paddingRight="5dip"/>
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/comment"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/edit_delete"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/MinusButton"
|
||||||
|
android:layout_gravity="center_vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</org.thialfihar.android.apg.ui.widget.UserIdEditor>
|
95
res/layout/encrypt_message.xml
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/message"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="top"
|
||||||
|
android:inputType="text|textCapSentences|textMultiLine|textLongMessage">
|
||||||
|
</EditText>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent" android:paddingBottom="3dip">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:text="@string/sign"
|
||||||
|
android:id="@+id/sign"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="5dip">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id"
|
||||||
|
android:text="Main User Id"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="right">
|
||||||
|
</TextView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id_rest"
|
||||||
|
android:text="Main User Id Rest"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" android:layout_gravity="right">
|
||||||
|
</TextView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@android:style/ButtonBar">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:text="@string/btn_selectEncryptKeys"
|
||||||
|
android:id="@+id/btn_selectEncryptKeys"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_send"
|
||||||
|
android:text="@string/btn_send"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
72
res/layout/key_list_child_item_master_key.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:singleLine="true"
|
||||||
|
android:paddingLeft="10dip"
|
||||||
|
android:layout_marginRight="?android:attr/scrollbarSize"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight" android:layout_width="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingRight="3dip">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_master_key"
|
||||||
|
android:src="@drawable/key_small"
|
||||||
|
android:paddingRight="6dip"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" android:layout_gravity="center_vertical"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_id"
|
||||||
|
android:text="Key ID"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" android:paddingRight="5dip" android:typeface="monospace"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_details"
|
||||||
|
android:text="(RSA, 1024bit)"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:gravity="right"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent" android:paddingBottom="2dip" android:paddingTop="2dip" android:layout_height="fill_parent" android:layout_gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_encrypt_key"
|
||||||
|
android:src="@drawable/encrypted_small"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_sign_key"
|
||||||
|
android:src="@drawable/signed_small"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
70
res/layout/key_list_child_item_sub_key.xml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:singleLine="true"
|
||||||
|
android:paddingLeft="40dip"
|
||||||
|
android:layout_marginRight="?android:attr/scrollbarSize"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||||
|
android:layout_width="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingRight="3dip">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_id"
|
||||||
|
android:text="Key ID"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" android:paddingRight="5dip" android:typeface="monospace"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_details"
|
||||||
|
android:text="(RSA, 1024bit)"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:gravity="right"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:paddingBottom="2dip"
|
||||||
|
android:paddingTop="2dip"
|
||||||
|
android:layout_gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_encrypt_key"
|
||||||
|
android:src="@drawable/encrypted_small"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_sign_key"
|
||||||
|
android:src="@drawable/signed_small"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
33
res/layout/key_list_child_item_user_id.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:paddingLeft="40dip"
|
||||||
|
android:layout_marginRight="?android:attr/scrollbarSize"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/user_id"
|
||||||
|
android:text="User ID"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingRight="3dip"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
52
res/layout/key_list_group_item.xml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:singleLine="true"
|
||||||
|
android:layout_marginRight="?android:attr/scrollbarSize"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content" android:paddingLeft="36dip">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id"
|
||||||
|
android:text="Main User ID"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id_rest"
|
||||||
|
android:text="<user@somewhere.com>"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
57
res/layout/mailbox_message_item.xml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:singleLine="true"
|
||||||
|
android:paddingLeft="3dip"
|
||||||
|
android:layout_marginRight="?android:attr/scrollbarSize"
|
||||||
|
android:paddingTop="3dip"
|
||||||
|
android:paddingBottom="3dip"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||||
|
android:layout_width="fill_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_encrypted"
|
||||||
|
android:src="@drawable/encrypted"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="5dip"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subject"
|
||||||
|
android:text="Subject"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/email_address"
|
||||||
|
android:text="user@somewhere.com"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
61
res/layout/main.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:paddingTop="5dip"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_marginTop="10dip"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/account_list"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@android:style/ButtonBar">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_encryptMessage"
|
||||||
|
android:text="@string/btn_encryptMessage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_decryptMessage"
|
||||||
|
android:text="@string/btn_decryptMessage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
54
res/layout/select_public_key.xml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:choiceMode="multipleChoice"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_weight="1">
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@android:style/ButtonBar">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:text="@android:string/ok"
|
||||||
|
android:id="@+id/btn_ok"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
android:id="@+id/btn_cancel"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
96
res/layout/select_public_key_item.xml
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:singleLine="true"
|
||||||
|
android:paddingLeft="3dip"
|
||||||
|
android:paddingRight="?android:attr/scrollbarSize"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||||
|
android:layout_width="fill_parent">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/selected"
|
||||||
|
android:focusable="false"
|
||||||
|
android:focusableInTouchMode="false"
|
||||||
|
android:clickable="false"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="5dip"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id"
|
||||||
|
android:text="Main User ID"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id_rest"
|
||||||
|
android:text="<user@somewhere.com>"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/right_column"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:minWidth="90dip"
|
||||||
|
android:paddingLeft="3dip"
|
||||||
|
android:gravity="right"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_id"
|
||||||
|
android:text="BBBBBBBB"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:typeface="monospace"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/creation"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:text="31.12.2009"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/expiry"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:text="31.12.2010"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:text="expired"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
30
res/layout/select_secret_key.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
88
res/layout/select_secret_key_item.xml
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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:singleLine="true"
|
||||||
|
android:paddingLeft="3dip"
|
||||||
|
android:paddingRight="?android:attr/scrollbarSize"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||||
|
android:layout_width="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="5dip"
|
||||||
|
android:paddingRight="5dip"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id"
|
||||||
|
android:text="Main User ID"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id_rest"
|
||||||
|
android:text="<user@somewhere.com>"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:minWidth="90dip"
|
||||||
|
android:paddingLeft="3dip"
|
||||||
|
android:gravity="right"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_id"
|
||||||
|
android:text="BBBBBBBB"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:typeface="monospace"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="fill_parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/creation"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:text="31.12.2009"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/expiry"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:text="31.12.2010"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:text="expired"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
68
res/values/strings.xml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">APG</string>
|
||||||
|
<string name="title_mailInbox">Mail Inbox</string>
|
||||||
|
<string name="title_managePublicKeys">Manage Public Keys</string>
|
||||||
|
<string name="title_manageSecretKeys">Manage Secret Keys</string>
|
||||||
|
<string name="title_selectRecipients">Select Recipients</string>
|
||||||
|
<string name="title_selectSignature">Select Signature</string>
|
||||||
|
<string name="title_encryptMessage">Encrypt Message</string>
|
||||||
|
<string name="title_decryptMessage">Decrypt Message</string>
|
||||||
|
<string name="title_authentification">Authentification</string>
|
||||||
|
<string name="title_createKey">Create Key</string>
|
||||||
|
<string name="title_editKey">Edit Key</string>
|
||||||
|
|
||||||
|
<string name="section_userIds">User IDs</string>
|
||||||
|
<string name="section_keys">Keys</string>
|
||||||
|
|
||||||
|
<string name="btn_send">Send via Email</string>
|
||||||
|
<string name="btn_decrypt">Decrypt</string>
|
||||||
|
<string name="btn_selectEncryptKeys">Select Recipients</string>
|
||||||
|
<string name="btn_reply">Reply</string>
|
||||||
|
<string name="btn_encryptMessage">Encrypt Message</string>
|
||||||
|
<string name="btn_decryptMessage">Decrypt Message</string>
|
||||||
|
<string name="btn_save">Save</string>
|
||||||
|
<string name="btn_doNotSave">Cancel</string>
|
||||||
|
|
||||||
|
<string name="menu_about">About</string>
|
||||||
|
<string name="menu_addAccount">Add GMail Account</string>
|
||||||
|
<string name="menu_managePublicKeys">Manage Public Keys</string>
|
||||||
|
<string name="menu_manageSecretKeys">Manage Secret Keys</string>
|
||||||
|
|
||||||
|
<string name="sign">Sign</string>
|
||||||
|
<string name="sign_as">Sign as</string>
|
||||||
|
<string name="no_keys_selected">Select Recipients</string>
|
||||||
|
<string name="one_key_selected">1 Recipient</string>
|
||||||
|
<string name="n_keys_selected">Recipients</string>
|
||||||
|
<string name="unknown_user_id"><unknown></string>
|
||||||
|
<string name="none"><none></string>
|
||||||
|
|
||||||
|
<string name="sign_only">Sign only</string>
|
||||||
|
<string name="encrypt_only">Encrypt only</string>
|
||||||
|
<string name="sign_and_encrypt">Sign and Encrypt</string>
|
||||||
|
|
||||||
|
<string name="dsa">DSA</string>
|
||||||
|
<string name="elgamal">ElGamal</string>
|
||||||
|
<string name="rsa">RSA</string>
|
||||||
|
|
||||||
|
<string name="wrong_pass_phrase">Wrong pass phrase.</string>
|
||||||
|
<string name="using_clipboard_content">Using clipboard content.</string>
|
||||||
|
<string name="key_saved">Key saved.</string>
|
||||||
|
<string name="set_a_pass_phrase">Set a pass phrase via the option menu first.</string>
|
||||||
|
</resources>
|
||||||
|
|
27
res/values/styles.xml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<style name="MinusButton">
|
||||||
|
<item name="android:background">@drawable/btn_circle</item>
|
||||||
|
<item name="android:src">@drawable/ic_btn_round_minus</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="PlusButton">
|
||||||
|
<item name="android:background">@drawable/btn_circle</item>
|
||||||
|
<item name="android:src">@drawable/ic_btn_round_plus</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
1496
src/org/thialfihar/android/apg/Apg.java
Normal file
118
src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import org.bouncycastle2.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle2.openpgp.PGPException;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.text.method.PasswordTransformationMethod;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnKeyListener;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
public class AskForSecretKeyPassPhrase {
|
||||||
|
public static final int DIALOG_PASS_PHRASE = 12345;
|
||||||
|
|
||||||
|
public static interface PassPhraseCallbackInterface {
|
||||||
|
void passPhraseCallback(String passPhrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dialog createDialog(Activity context, long secretKeyId,
|
||||||
|
PassPhraseCallbackInterface callback) {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(context);
|
||||||
|
|
||||||
|
final PGPSecretKey secretKey =
|
||||||
|
Apg.getMasterKey(Apg.findSecretKeyRing(secretKeyId));
|
||||||
|
if (secretKey == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String userId = Apg.getMainUserIdSafe(context, secretKey);
|
||||||
|
|
||||||
|
alert.setTitle(R.string.title_authentification);
|
||||||
|
alert.setMessage("Pass phrase for " + userId);
|
||||||
|
|
||||||
|
final EditText input = new EditText(context);
|
||||||
|
input.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
input.setTransformationMethod(new PasswordTransformationMethod());
|
||||||
|
input.setOnKeyListener(new OnKeyListener() {
|
||||||
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
|
// If the event is a key-down event on the "enter" button
|
||||||
|
if (event.getAction() == KeyEvent.ACTION_DOWN &&
|
||||||
|
keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||||
|
try {
|
||||||
|
((AlertDialog) v.getParent()).getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
|
.performClick();
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// don't do anything if we're not in that dialog
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 5dip padding
|
||||||
|
int padding = (int) (10 * context.getResources().getDisplayMetrics().densityDpi / 160);
|
||||||
|
LinearLayout layout = new LinearLayout(context);
|
||||||
|
layout.setPadding(padding, 0, padding, 0);
|
||||||
|
layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
|
||||||
|
LayoutParams.WRAP_CONTENT));
|
||||||
|
input.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
|
||||||
|
LayoutParams.WRAP_CONTENT));
|
||||||
|
layout.addView(input);
|
||||||
|
alert.setView(layout);
|
||||||
|
|
||||||
|
final PassPhraseCallbackInterface cb = callback;
|
||||||
|
final Activity activity = context;
|
||||||
|
alert.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
activity.removeDialog(DIALOG_PASS_PHRASE);
|
||||||
|
String passPhrase = "" + input.getText();
|
||||||
|
try {
|
||||||
|
secretKey.extractPrivateKey(passPhrase.toCharArray(),
|
||||||
|
new BouncyCastleProvider());
|
||||||
|
} catch (PGPException e) {
|
||||||
|
Toast.makeText(activity,
|
||||||
|
R.string.wrong_pass_phrase,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.passPhraseCallback(passPhrase);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
activity.removeDialog(DIALOG_PASS_PHRASE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
}
|
343
src/org/thialfihar/android/apg/DecryptMessageActivity.java
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
import org.bouncycastle2.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle2.openpgp.PGPException;
|
||||||
|
import org.bouncycastle2.util.Strings;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.text.ClipboardManager;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
public class DecryptMessageActivity extends Activity
|
||||||
|
implements Runnable, ProgressDialogUpdater,
|
||||||
|
AskForSecretKeyPassPhrase.PassPhraseCallbackInterface {
|
||||||
|
static final int GET_PUCLIC_KEYS = 1;
|
||||||
|
static final int GET_SECRET_KEY = 2;
|
||||||
|
|
||||||
|
static final int DIALOG_DECRYPTING = 1;
|
||||||
|
|
||||||
|
static final int MESSAGE_PROGRESS_UPDATE = 1;
|
||||||
|
static final int MESSAGE_DONE = 2;
|
||||||
|
|
||||||
|
private long mDecryptionKeyId = 0;
|
||||||
|
private long mSignatureKeyId = 0;
|
||||||
|
|
||||||
|
private String mReplyTo = null;
|
||||||
|
private String mSubject = null;
|
||||||
|
|
||||||
|
private ProgressDialog mProgressDialog = null;
|
||||||
|
private Thread mRunningThread = null;
|
||||||
|
|
||||||
|
private EditText mMessage = null;
|
||||||
|
private LinearLayout mSignatureLayout = null;
|
||||||
|
private ImageView mSignatureStatusImage = null;
|
||||||
|
private TextView mUserId = null;
|
||||||
|
private TextView mUserIdRest = null;
|
||||||
|
private Button mDecryptButton = null;
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
Bundle data = msg.getData();
|
||||||
|
if (data != null) {
|
||||||
|
int type = data.getInt("type");
|
||||||
|
switch (type) {
|
||||||
|
case MESSAGE_PROGRESS_UPDATE: {
|
||||||
|
String message = data.getString("message");
|
||||||
|
if (mProgressDialog != null) {
|
||||||
|
if (message != null) {
|
||||||
|
mProgressDialog.setMessage(message);
|
||||||
|
}
|
||||||
|
mProgressDialog.setMax(data.getInt("max"));
|
||||||
|
mProgressDialog.setProgress(data.getInt("progress"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE_DONE: {
|
||||||
|
removeDialog(DIALOG_DECRYPTING);
|
||||||
|
mProgressDialog = null;
|
||||||
|
mSignatureKeyId = 0;
|
||||||
|
String error = data.getString("error");
|
||||||
|
String decryptedMessage = data.getString("decryptedMessage");
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(DecryptMessageActivity.this,
|
||||||
|
"Error: " + data.getString("error"),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
mSignatureLayout.setVisibility(View.INVISIBLE);
|
||||||
|
if (decryptedMessage != null) {
|
||||||
|
mMessage.setText(decryptedMessage);
|
||||||
|
mDecryptButton.setText(R.string.btn_reply);
|
||||||
|
mDecryptButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
replyClicked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.getBoolean("signature")) {
|
||||||
|
String userId = data.getString("signatureUserId");
|
||||||
|
mSignatureKeyId = data.getLong("signatureKeyId");
|
||||||
|
mUserIdRest.setText("id: " +
|
||||||
|
Long.toHexString(mSignatureKeyId & 0xffffffffL));
|
||||||
|
if (userId == null) {
|
||||||
|
userId =
|
||||||
|
getResources()
|
||||||
|
.getString(
|
||||||
|
R.string.unknown_user_id);
|
||||||
|
}
|
||||||
|
String chunks[] = userId.split(" <", 2);
|
||||||
|
userId = chunks[0];
|
||||||
|
if (chunks.length > 1) {
|
||||||
|
mUserIdRest.setText("<" + chunks[1]);
|
||||||
|
}
|
||||||
|
mUserId.setText(userId);
|
||||||
|
|
||||||
|
if (data.getBoolean("signatureSuccess")) {
|
||||||
|
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||||
|
} else if (data.getBoolean("signatureUnknown")) {
|
||||||
|
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||||
|
} else {
|
||||||
|
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||||
|
}
|
||||||
|
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.decrypt_message);
|
||||||
|
|
||||||
|
Apg.initialize(this);
|
||||||
|
|
||||||
|
mMessage = (EditText) findViewById(R.id.message);
|
||||||
|
mDecryptButton = (Button) findViewById(R.id.btn_decrypt);
|
||||||
|
mSignatureLayout = (LinearLayout) findViewById(R.id.layout_signature);
|
||||||
|
mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status);
|
||||||
|
mUserId = (TextView) findViewById(R.id.main_user_id);
|
||||||
|
mUserIdRest = (TextView) findViewById(R.id.main_user_id_rest);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_VIEW)) {
|
||||||
|
Uri uri = intent.getData();
|
||||||
|
try {
|
||||||
|
InputStream attachment = getContentResolver().openInputStream(uri);
|
||||||
|
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||||
|
byte bytes[] = new byte[1 << 16];
|
||||||
|
int length;
|
||||||
|
while ((length = attachment.read(bytes)) > 0) {
|
||||||
|
byteOut.write(bytes, 0, length);
|
||||||
|
}
|
||||||
|
byteOut.close();
|
||||||
|
String data = Strings.fromUTF8ByteArray(byteOut.toByteArray());
|
||||||
|
mMessage.setText(data);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// ignore, then
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore, then
|
||||||
|
}
|
||||||
|
} else if (intent.getAction() != null && intent.getAction().equals(Apg.Intent.DECRYPT)) {
|
||||||
|
String data = intent.getExtras().getString("data");
|
||||||
|
if (data != null) {
|
||||||
|
Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
data = matcher.group(1);
|
||||||
|
mMessage.setText(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mReplyTo = intent.getExtras().getString("replyTo");
|
||||||
|
mSubject = intent.getExtras().getString("subject");
|
||||||
|
} else {
|
||||||
|
ClipboardManager clip = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
String data = "";
|
||||||
|
Matcher matcher = Apg.PGP_MESSAGE.matcher(clip.getText());
|
||||||
|
if (matcher.matches()) {
|
||||||
|
data = matcher.group(1);
|
||||||
|
mMessage.setText(data);
|
||||||
|
Toast.makeText(this, R.string.using_clipboard_content, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mSignatureLayout.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
|
mDecryptButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
decryptClicked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mMessage.getText().length() > 0) {
|
||||||
|
mDecryptButton.performClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case DIALOG_DECRYPTING: {
|
||||||
|
mProgressDialog = new ProgressDialog(this);
|
||||||
|
mProgressDialog.setMessage("initializing...");
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
return mProgressDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AskForSecretKeyPassPhrase.DIALOG_PASS_PHRASE: {
|
||||||
|
return AskForSecretKeyPassPhrase.createDialog(this, mDecryptionKeyId, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onCreateDialog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(String message, int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putString("message", message);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decryptClicked() {
|
||||||
|
String error = null;
|
||||||
|
ByteArrayInputStream in =
|
||||||
|
new ByteArrayInputStream(mMessage.getText().toString().getBytes());
|
||||||
|
try {
|
||||||
|
mDecryptionKeyId = Apg.getDecryptionKeyId(in);
|
||||||
|
showDialog(AskForSecretKeyPassPhrase.DIALOG_PASS_PHRASE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
error = e.getLocalizedMessage();
|
||||||
|
} catch (Apg.GeneralException e) {
|
||||||
|
error = e.getLocalizedMessage();
|
||||||
|
}
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(this, "Error: " + error, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replyClicked() {
|
||||||
|
Intent intent = new Intent(this, EncryptMessageActivity.class);
|
||||||
|
intent.setAction(Apg.Intent.ENCRYPT);
|
||||||
|
String data = mMessage.getText().toString();
|
||||||
|
data = data.replaceAll("(?m)^", "> ");
|
||||||
|
data = "\n\n" + data;
|
||||||
|
intent.putExtra("data", data);
|
||||||
|
intent.putExtra("subject", "Re: " + mSubject);
|
||||||
|
intent.putExtra("sendTo", mReplyTo);
|
||||||
|
intent.putExtra("eyId", mSignatureKeyId);
|
||||||
|
intent.putExtra("signatureKeyId", mDecryptionKeyId);
|
||||||
|
intent.putExtra("encryptionKeyIds", new long[] { mSignatureKeyId });
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void passPhraseCallback(String passPhrase) {
|
||||||
|
Apg.setPassPhrase(passPhrase);
|
||||||
|
decryptStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decryptStart() {
|
||||||
|
showDialog(DIALOG_DECRYPTING);
|
||||||
|
mRunningThread = new Thread(this);
|
||||||
|
mRunningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
String error = null;
|
||||||
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
Message msg = new Message();
|
||||||
|
|
||||||
|
ByteArrayInputStream in =
|
||||||
|
new ByteArrayInputStream(mMessage.getText().toString().getBytes());
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = Apg.decrypt(in, out, Apg.getPassPhrase(), this);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (IOException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (Apg.GeneralException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
data.putInt("type", MESSAGE_DONE);
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
data.putString("error", error);
|
||||||
|
} else {
|
||||||
|
data.putString("decryptedMessage", Strings.fromUTF8ByteArray(out.toByteArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
401
src/org/thialfihar/android/apg/EditKeyActivity.java
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.bouncycastle2.openpgp.PGPException;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.thialfihar.android.apg.ui.widget.SectionView;
|
||||||
|
import org.thialfihar.android.apg.utils.IterableIterator;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.text.method.PasswordTransformationMethod;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
public class EditKeyActivity extends Activity
|
||||||
|
implements OnClickListener, ProgressDialogUpdater, Runnable {
|
||||||
|
static final int OPTION_MENU_NEW_PASS_PHRASE = 1;
|
||||||
|
|
||||||
|
static final int DIALOG_NEW_PASS_PHRASE = 1;
|
||||||
|
static final int DIALOG_PASS_PHRASES_DO_NOT_MATCH = 2;
|
||||||
|
static final int DIALOG_NO_PASS_PHRASE = 3;
|
||||||
|
static final int DIALOG_SAVING = 4;
|
||||||
|
|
||||||
|
static final int MESSAGE_PROGRESS_UPDATE = 1;
|
||||||
|
static final int MESSAGE_DONE = 2;
|
||||||
|
|
||||||
|
private PGPSecretKeyRing mKeyRing = null;
|
||||||
|
|
||||||
|
private SectionView mUserIds;
|
||||||
|
private SectionView mKeys;
|
||||||
|
|
||||||
|
private Button mSaveButton;
|
||||||
|
private Button mDiscardButton;
|
||||||
|
|
||||||
|
private ProgressDialog mProgressDialog = null;
|
||||||
|
private Thread mRunningThread = null;
|
||||||
|
|
||||||
|
private String mNewPassPhrase = null;
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
Bundle data = msg.getData();
|
||||||
|
if (data != null) {
|
||||||
|
int type = data.getInt("type");
|
||||||
|
switch (type) {
|
||||||
|
case MESSAGE_PROGRESS_UPDATE: {
|
||||||
|
String message = data.getString("message");
|
||||||
|
if (mProgressDialog != null) {
|
||||||
|
if (message != null) {
|
||||||
|
mProgressDialog.setMessage(message);
|
||||||
|
}
|
||||||
|
mProgressDialog.setMax(data.getInt("max"));
|
||||||
|
mProgressDialog.setProgress(data.getInt("progress"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE_DONE: {
|
||||||
|
removeDialog(DIALOG_SAVING);
|
||||||
|
mProgressDialog = null;
|
||||||
|
|
||||||
|
String error = data.getString("error");
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(EditKeyActivity.this,
|
||||||
|
"Error: " + data.getString("error"),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(EditKeyActivity.this, R.string.key_saved,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.edit_key);
|
||||||
|
|
||||||
|
Vector<String> userIds = new Vector<String>();
|
||||||
|
Vector<PGPSecretKey> keys = new Vector<PGPSecretKey>();
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
long keyId = 0;
|
||||||
|
if (intent.getExtras() != null) {
|
||||||
|
keyId = intent.getExtras().getLong("keyId");
|
||||||
|
}
|
||||||
|
if (keyId != 0) {
|
||||||
|
PGPSecretKey masterKey = null;
|
||||||
|
mKeyRing = Apg.getSecretKeyRing(keyId);
|
||||||
|
if (mKeyRing != null) {
|
||||||
|
masterKey = Apg.getMasterKey(mKeyRing);
|
||||||
|
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
|
||||||
|
keys.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (masterKey != null) {
|
||||||
|
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||||
|
userIds.add(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Apg.getPassPhrase() == null) {
|
||||||
|
Apg.setPassPhrase("");
|
||||||
|
}
|
||||||
|
|
||||||
|
mSaveButton = (Button) findViewById(R.id.btn_save);
|
||||||
|
mDiscardButton = (Button) findViewById(R.id.btn_discard);
|
||||||
|
|
||||||
|
mSaveButton.setOnClickListener(this);
|
||||||
|
mDiscardButton.setOnClickListener(this);
|
||||||
|
|
||||||
|
LayoutInflater inflater =
|
||||||
|
(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
|
||||||
|
LinearLayout container = (LinearLayout) findViewById(R.id.container);
|
||||||
|
mUserIds = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||||
|
mUserIds.setType(SectionView.TYPE_USER_ID);
|
||||||
|
mUserIds.setUserIds(userIds);
|
||||||
|
container.addView(mUserIds);
|
||||||
|
mKeys = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||||
|
mKeys.setType(SectionView.TYPE_KEY);
|
||||||
|
mKeys.setKeys(keys);
|
||||||
|
container.addView(mKeys);
|
||||||
|
|
||||||
|
Toast.makeText(this, "Warning: Key editing is still kind of beta.", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean havePassPhrase() {
|
||||||
|
return (Apg.getPassPhrase() != null && !Apg.getPassPhrase().equals("")) ||
|
||||||
|
(mNewPassPhrase != null && mNewPassPhrase.equals(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
menu.add(0, OPTION_MENU_NEW_PASS_PHRASE, 0,
|
||||||
|
(havePassPhrase() ? "Change Pass Phrase" : "Set Pass Phrase"))
|
||||||
|
.setIcon(android.R.drawable.ic_menu_add);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case OPTION_MENU_NEW_PASS_PHRASE: {
|
||||||
|
showDialog(DIALOG_NEW_PASS_PHRASE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case DIALOG_SAVING: {
|
||||||
|
mProgressDialog = new ProgressDialog(this);
|
||||||
|
mProgressDialog.setMessage("saving...");
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
return mProgressDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_NEW_PASS_PHRASE: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
if (havePassPhrase()) {
|
||||||
|
alert.setTitle("Change Pass Phrase");
|
||||||
|
} else {
|
||||||
|
alert.setTitle("Set Pass Phrase");
|
||||||
|
}
|
||||||
|
alert.setMessage("Enter the pass phrase twice.");
|
||||||
|
|
||||||
|
final EditText input1 = new EditText(this);
|
||||||
|
final EditText input2 = new EditText(this);
|
||||||
|
input1.setText("");
|
||||||
|
input2.setText("");
|
||||||
|
input1.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
input2.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
input1.setTransformationMethod(new PasswordTransformationMethod());
|
||||||
|
input2.setTransformationMethod(new PasswordTransformationMethod());
|
||||||
|
|
||||||
|
// 5dip padding
|
||||||
|
int padding = (int) (10 * getResources().getDisplayMetrics().densityDpi / 160);
|
||||||
|
LinearLayout layout = new LinearLayout(this);
|
||||||
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
layout.setPadding(padding, 0, padding, 0);
|
||||||
|
layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
|
||||||
|
LayoutParams.WRAP_CONTENT));
|
||||||
|
input1.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
|
||||||
|
LayoutParams.WRAP_CONTENT));
|
||||||
|
input2.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
|
||||||
|
LayoutParams.WRAP_CONTENT));
|
||||||
|
layout.addView(input1);
|
||||||
|
layout.addView(input2);
|
||||||
|
|
||||||
|
alert.setView(layout);
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(DIALOG_NEW_PASS_PHRASE);
|
||||||
|
|
||||||
|
String passPhrase1 = "" + input1.getText();
|
||||||
|
String passPhrase2 = "" + input2.getText();
|
||||||
|
if (!passPhrase1.equals(passPhrase2)) {
|
||||||
|
showDialog(DIALOG_PASS_PHRASES_DO_NOT_MATCH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passPhrase1.equals("")) {
|
||||||
|
showDialog(DIALOG_NO_PASS_PHRASE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mNewPassPhrase = passPhrase1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(DIALOG_NEW_PASS_PHRASE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_PASS_PHRASES_DO_NOT_MATCH: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||||
|
alert.setTitle("Error");
|
||||||
|
alert.setMessage("The pass phrases didn't match.");
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(DIALOG_PASS_PHRASES_DO_NOT_MATCH);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alert.setCancelable(false);
|
||||||
|
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_NO_PASS_PHRASE: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||||
|
alert.setTitle("Error");
|
||||||
|
alert.setMessage("Empty pass phrases are not supported.");
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(DIALOG_NO_PASS_PHRASE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alert.setCancelable(false);
|
||||||
|
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onCreateDialog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (v == mSaveButton) {
|
||||||
|
// TODO: some warning
|
||||||
|
saveClicked();
|
||||||
|
} else if (v == mDiscardButton) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveClicked() {
|
||||||
|
if ((Apg.getPassPhrase() == null || Apg.getPassPhrase().equals("")) &&
|
||||||
|
(mNewPassPhrase == null || mNewPassPhrase.equals(""))) {
|
||||||
|
Toast.makeText(this, R.string.set_a_pass_phrase, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showDialog(DIALOG_SAVING);
|
||||||
|
mRunningThread = new Thread(this);
|
||||||
|
mRunningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
String error = null;
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
Message msg = new Message();
|
||||||
|
|
||||||
|
try {
|
||||||
|
String oldPassPhrase = Apg.getPassPhrase();
|
||||||
|
String newPassPhrase = mNewPassPhrase;
|
||||||
|
if (newPassPhrase == null) {
|
||||||
|
newPassPhrase = oldPassPhrase;
|
||||||
|
}
|
||||||
|
Apg.buildSecretKey(this, mUserIds, mKeys, oldPassPhrase, newPassPhrase, this);
|
||||||
|
} catch (NoSuchProviderException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (PGPException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (Apg.GeneralException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
data.putInt("type", MESSAGE_DONE);
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
data.putString("error", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(String message, int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putString("message", message);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
428
src/org/thialfihar/android/apg/EncryptMessageActivity.java
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.bouncycastle2.openpgp.PGPException;
|
||||||
|
import org.bouncycastle2.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle2.util.Strings;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
public class EncryptMessageActivity extends Activity
|
||||||
|
implements Runnable, ProgressDialogUpdater,
|
||||||
|
AskForSecretKeyPassPhrase.PassPhraseCallbackInterface {
|
||||||
|
static final int GET_PUCLIC_KEYS = 1;
|
||||||
|
static final int GET_SECRET_KEY = 2;
|
||||||
|
|
||||||
|
static final int DIALOG_ENCRYPTING = 1;
|
||||||
|
|
||||||
|
static final int MESSAGE_PROGRESS_UPDATE = 1;
|
||||||
|
static final int MESSAGE_DONE = 2;
|
||||||
|
|
||||||
|
private String mSubject = null;
|
||||||
|
private String mSendTo = null;
|
||||||
|
|
||||||
|
private long mEncryptionKeyIds[] = null;
|
||||||
|
private long mSignatureKeyId = 0;
|
||||||
|
|
||||||
|
private ProgressDialog mProgressDialog = null;
|
||||||
|
private Thread mRunningThread = null;
|
||||||
|
|
||||||
|
private EditText mMessage = null;
|
||||||
|
private Button mSelectKeysButton = null;
|
||||||
|
private Button mSendButton = null;
|
||||||
|
private CheckBox mSign = null;
|
||||||
|
private TextView mMainUserId = null;
|
||||||
|
private TextView mMainUserIdRest = null;
|
||||||
|
|
||||||
|
private Handler mhandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message mSg) {
|
||||||
|
Bundle data = mSg.getData();
|
||||||
|
if (data != null) {
|
||||||
|
int type = data.getInt("type");
|
||||||
|
switch (type) {
|
||||||
|
case MESSAGE_PROGRESS_UPDATE: {
|
||||||
|
String message = data.getString("message");
|
||||||
|
if (mProgressDialog != null) {
|
||||||
|
if (message != null) {
|
||||||
|
mProgressDialog.setMessage(message);
|
||||||
|
}
|
||||||
|
mProgressDialog.setMax(data.getInt("max"));
|
||||||
|
mProgressDialog.setProgress(data.getInt("progress"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE_DONE: {
|
||||||
|
removeDialog(DIALOG_ENCRYPTING);
|
||||||
|
mProgressDialog = null;
|
||||||
|
|
||||||
|
String error = data.getString("error");
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(EncryptMessageActivity.this,
|
||||||
|
"Error: " + data.getString("error"),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
String message = data.getString("message");
|
||||||
|
String signature = data.getString("signature");
|
||||||
|
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||||
|
emailIntent.setType("text/plain; charset=utf-8");
|
||||||
|
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, message);
|
||||||
|
if (signature != null) {
|
||||||
|
String fullText = "-----BEGIN PGP SIGNED MESSAGE-----\n" +
|
||||||
|
"Hash: SHA256\n" + "\n" +
|
||||||
|
message + "\n" + signature;
|
||||||
|
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, fullText);
|
||||||
|
}
|
||||||
|
if (mSubject != null) {
|
||||||
|
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
|
||||||
|
mSubject);
|
||||||
|
}
|
||||||
|
if (mSendTo != null) {
|
||||||
|
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
|
||||||
|
new String[] { mSendTo });
|
||||||
|
}
|
||||||
|
EncryptMessageActivity.this.
|
||||||
|
startActivity(Intent.createChooser(emailIntent, "Send mail..."));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mhandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(String message, int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putString("message", message);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mhandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.encrypt_message);
|
||||||
|
|
||||||
|
Apg.initialize(this);
|
||||||
|
|
||||||
|
mMessage = (EditText) findViewById(R.id.message);
|
||||||
|
mSelectKeysButton = (Button) findViewById(R.id.btn_selectEncryptKeys);
|
||||||
|
mSendButton = (Button) findViewById(R.id.btn_send);
|
||||||
|
mSign = (CheckBox) findViewById(R.id.sign);
|
||||||
|
mMainUserId = (TextView) findViewById(R.id.main_user_id);
|
||||||
|
mMainUserIdRest = (TextView) findViewById(R.id.main_user_id_rest);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if (intent.getAction() != null &&
|
||||||
|
intent.getAction().equals(Apg.Intent.ENCRYPT)) {
|
||||||
|
String data = intent.getExtras().getString("data");
|
||||||
|
mSendTo = intent.getExtras().getString("sendTo");
|
||||||
|
mSubject = intent.getExtras().getString("subject");
|
||||||
|
long signatureKeyId = intent.getExtras().getLong("signatureKeyId");
|
||||||
|
long encryptionKeyIds[] = intent.getExtras().getLongArray("encryptionKeyIds");
|
||||||
|
if (signatureKeyId != 0) {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.findSecretKeyRing(signatureKeyId);
|
||||||
|
PGPSecretKey masterKey = null;
|
||||||
|
if (keyRing != null) {
|
||||||
|
masterKey = Apg.getMasterKey(keyRing);
|
||||||
|
if (masterKey != null) {
|
||||||
|
Vector<PGPSecretKey> signKeys = Apg.getUsableSigningKeys(keyRing);
|
||||||
|
if (signKeys.size() > 0) {
|
||||||
|
mSignatureKeyId = masterKey.getKeyID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encryptionKeyIds != null) {
|
||||||
|
Vector<Long> goodIds = new Vector<Long>();
|
||||||
|
for (int i = 0; i < encryptionKeyIds.length; ++i) {
|
||||||
|
PGPPublicKeyRing keyRing = Apg.findPublicKeyRing(encryptionKeyIds[i]);
|
||||||
|
PGPPublicKey masterKey = null;
|
||||||
|
if (keyRing == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
masterKey = Apg.getMasterKey(keyRing);
|
||||||
|
if (masterKey == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Vector<PGPPublicKey> encryptKeys = Apg.getUsableEncryptKeys(keyRing);
|
||||||
|
if (encryptKeys.size() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
goodIds.add(masterKey.getKeyID());
|
||||||
|
}
|
||||||
|
if (goodIds.size() > 0) {
|
||||||
|
mEncryptionKeyIds = new long[goodIds.size()];
|
||||||
|
for (int i = 0; i < goodIds.size(); ++i) {
|
||||||
|
mEncryptionKeyIds[i] = goodIds.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data != null) {
|
||||||
|
mMessage.setText(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mSendButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
sendClicked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mSelectKeysButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
selectPublicKeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mSign.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
CheckBox checkBox = (CheckBox) v;
|
||||||
|
if (checkBox.isChecked()) {
|
||||||
|
selectSecretKey();
|
||||||
|
} else {
|
||||||
|
mSignatureKeyId = 0;
|
||||||
|
Apg.setPassPhrase(null);
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case DIALOG_ENCRYPTING: {
|
||||||
|
mProgressDialog = new ProgressDialog(this);
|
||||||
|
mProgressDialog.setMessage("initializing...");
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
return mProgressDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AskForSecretKeyPassPhrase.DIALOG_PASS_PHRASE: {
|
||||||
|
return AskForSecretKeyPassPhrase.createDialog(this, mSignatureKeyId, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onCreateDialog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendClicked() {
|
||||||
|
if (mSignatureKeyId != 0 && Apg.getPassPhrase() == null) {
|
||||||
|
showDialog(AskForSecretKeyPassPhrase.DIALOG_PASS_PHRASE);
|
||||||
|
} else {
|
||||||
|
encryptStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void passPhraseCallback(String passPhrase) {
|
||||||
|
Apg.setPassPhrase(passPhrase);
|
||||||
|
encryptStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encryptStart() {
|
||||||
|
showDialog(DIALOG_ENCRYPTING);
|
||||||
|
mRunningThread = new Thread(this);
|
||||||
|
mRunningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
String error = null;
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
Message msg = new Message();
|
||||||
|
String message = mMessage.getText().toString();
|
||||||
|
// fix the message a bit, trailing spaces and newlines break stuff,
|
||||||
|
// because GMail sends as HTML and such things fuck up the signature,
|
||||||
|
// TODO: things like "<" and ">" also fuck up the signature
|
||||||
|
message = message.replaceAll(" +\n", "\n");
|
||||||
|
message = message.replaceAll("\n\n+", "\n\n");
|
||||||
|
message = message.replaceFirst("^\n+", "");
|
||||||
|
message = message.replaceFirst("\n+$", "");
|
||||||
|
ByteArrayInputStream in =
|
||||||
|
new ByteArrayInputStream(Strings.toUTF8ByteArray(message));
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) {
|
||||||
|
Apg.encrypt(in, out, mEncryptionKeyIds, mSignatureKeyId, Apg.getPassPhrase(), this);
|
||||||
|
data.putString("message", new String(out.toByteArray()));
|
||||||
|
} else {
|
||||||
|
Apg.sign(in, out, mSignatureKeyId, Apg.getPassPhrase(), this);
|
||||||
|
data.putString("message", message);
|
||||||
|
data.putString("signature", new String(out.toByteArray()));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (PGPException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (NoSuchProviderException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (Apg.GeneralException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
data.putInt("type", MESSAGE_DONE);
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
data.putString("error", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.setData(data);
|
||||||
|
mhandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateView() {
|
||||||
|
if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
|
||||||
|
mSelectKeysButton.setText(R.string.no_keys_selected);
|
||||||
|
} else if (mEncryptionKeyIds.length == 1) {
|
||||||
|
mSelectKeysButton.setText(R.string.one_key_selected);
|
||||||
|
} else {
|
||||||
|
mSelectKeysButton.setText("" + mEncryptionKeyIds.length + " " +
|
||||||
|
getResources().getString(R.string.n_keys_selected));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSignatureKeyId == 0) {
|
||||||
|
mSign.setText(R.string.sign);
|
||||||
|
mSign.setChecked(false);
|
||||||
|
mMainUserId.setText("");
|
||||||
|
mMainUserIdRest.setText("");
|
||||||
|
} else {
|
||||||
|
String uid = getResources().getString(R.string.unknown_user_id);
|
||||||
|
String uidExtra = "";
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(mSignatureKeyId);
|
||||||
|
if (keyRing != null) {
|
||||||
|
PGPSecretKey key = Apg.getMasterKey(keyRing);
|
||||||
|
if (key != null) {
|
||||||
|
String userId = Apg.getMainUserIdSafe(this, key);
|
||||||
|
String chunks[] = userId.split(" <", 2);
|
||||||
|
uid = chunks[0];
|
||||||
|
if (chunks.length > 1) {
|
||||||
|
uidExtra = "<" + chunks[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mMainUserId.setText(uid);
|
||||||
|
mMainUserIdRest.setText(uidExtra);
|
||||||
|
mSign.setText(R.string.sign_as);
|
||||||
|
mSign.setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectPublicKeys() {
|
||||||
|
Intent intent = new Intent(this, SelectPublicKeyListActivity.class);
|
||||||
|
intent.putExtra("selection", mEncryptionKeyIds);
|
||||||
|
startActivityForResult(intent, GET_PUCLIC_KEYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectSecretKey() {
|
||||||
|
Intent intent = new Intent(this, SelectSecretKeyListActivity.class);
|
||||||
|
startActivityForResult(intent, GET_SECRET_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case GET_PUCLIC_KEYS: {
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
Bundle bundle = data.getExtras();
|
||||||
|
mEncryptionKeyIds = bundle.getLongArray("selection");
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_SECRET_KEY: {
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
Bundle bundle = data.getExtras();
|
||||||
|
long newId = bundle.getLong("selectedKeyId");
|
||||||
|
if (mSignatureKeyId != newId) {
|
||||||
|
Apg.setPassPhrase(null);
|
||||||
|
}
|
||||||
|
mSignatureKeyId = newId;
|
||||||
|
} else {
|
||||||
|
mSignatureKeyId = 0;
|
||||||
|
Apg.setPassPhrase(null);
|
||||||
|
}
|
||||||
|
updateView();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
202
src/org/thialfihar/android/apg/MailListActivity.java
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import java.util.Vector;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
import android.app.ListActivity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ListAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
|
||||||
|
public class MailListActivity extends ListActivity {
|
||||||
|
LayoutInflater minflater = null;
|
||||||
|
|
||||||
|
private class Conversation {
|
||||||
|
public long id;
|
||||||
|
public String subject;
|
||||||
|
public Vector<Message> messages;
|
||||||
|
|
||||||
|
public Conversation(long id, String subject) {
|
||||||
|
this.id = id;
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Message {
|
||||||
|
public Conversation parent;
|
||||||
|
public long id;
|
||||||
|
public String subject;
|
||||||
|
public String fromAddress;
|
||||||
|
public String data;
|
||||||
|
public String replyTo;
|
||||||
|
|
||||||
|
public Message(Conversation parent, long id, String subject,
|
||||||
|
String fromAddress, String replyTo, String data) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.id = id;
|
||||||
|
this.subject = subject;
|
||||||
|
this.fromAddress = fromAddress;
|
||||||
|
this.replyTo = replyTo;
|
||||||
|
this.data = data;
|
||||||
|
if (this.replyTo == null || this.replyTo.equals("")) {
|
||||||
|
this.replyTo = this.fromAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector<Conversation> mconversations;
|
||||||
|
private Vector<Message> mmessages;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
minflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
|
||||||
|
mconversations = new Vector<Conversation>();
|
||||||
|
mmessages = new Vector<Message>();
|
||||||
|
|
||||||
|
String account = getIntent().getExtras().getString("account");
|
||||||
|
// TODO: what if account is null?
|
||||||
|
Uri uri = Uri.parse("content://gmail-ls/conversations/" + account);
|
||||||
|
Cursor cursor =
|
||||||
|
managedQuery(uri, new String[] { "conversation_id", "subject" }, null, null, null);
|
||||||
|
for (int i = 0; i < cursor.getCount(); ++i) {
|
||||||
|
cursor.moveToPosition(i);
|
||||||
|
|
||||||
|
int idIndex = cursor.getColumnIndex("conversation_id");
|
||||||
|
int subjectIndex = cursor.getColumnIndex("subject");
|
||||||
|
long conversationId = cursor.getLong(idIndex);
|
||||||
|
Conversation conversation =
|
||||||
|
new Conversation(conversationId, cursor.getString(subjectIndex));
|
||||||
|
Uri messageUri = Uri.withAppendedPath(uri, "" + conversationId + "/messages");
|
||||||
|
Cursor messageCursor =
|
||||||
|
managedQuery(messageUri, new String[] {
|
||||||
|
"messageId",
|
||||||
|
"subject",
|
||||||
|
"fromAddress",
|
||||||
|
"replyToAddresses",
|
||||||
|
"body" }, null, null, null);
|
||||||
|
Vector<Message> messages = new Vector<Message>();
|
||||||
|
for (int j = 0; j < messageCursor.getCount(); ++j) {
|
||||||
|
messageCursor.moveToPosition(j);
|
||||||
|
idIndex = messageCursor.getColumnIndex("messageId");
|
||||||
|
subjectIndex = messageCursor.getColumnIndex("subject");
|
||||||
|
int fromAddressIndex = messageCursor.getColumnIndex("fromAddress");
|
||||||
|
int replyToIndex = messageCursor.getColumnIndex("replyToAddresses");
|
||||||
|
int bodyIndex = messageCursor.getColumnIndex("body");
|
||||||
|
String data = messageCursor.getString(bodyIndex);
|
||||||
|
data = Html.fromHtml(data).toString();
|
||||||
|
Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
data = matcher.group(1);
|
||||||
|
} else {
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
Message message =
|
||||||
|
new Message(conversation,
|
||||||
|
messageCursor.getLong(idIndex),
|
||||||
|
messageCursor.getString(subjectIndex),
|
||||||
|
messageCursor.getString(fromAddressIndex),
|
||||||
|
messageCursor.getString(replyToIndex), data);
|
||||||
|
|
||||||
|
messages.add(message);
|
||||||
|
mmessages.add(message);
|
||||||
|
}
|
||||||
|
conversation.messages = messages;
|
||||||
|
mconversations.add(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListAdapter(new MailboxAdapter());
|
||||||
|
getListView().setOnItemClickListener(new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> arg0, View v, int position, long id) {
|
||||||
|
Intent intent = new Intent(MailListActivity.this, DecryptMessageActivity.class);
|
||||||
|
intent.setAction(Apg.Intent.DECRYPT);
|
||||||
|
Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position);
|
||||||
|
intent.putExtra("data", message.data);
|
||||||
|
intent.putExtra("subject", message.subject);
|
||||||
|
intent.putExtra("replyTo", message.replyTo);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MailboxAdapter extends BaseAdapter implements ListAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(int position) {
|
||||||
|
Message message = (Message) getItem(position);
|
||||||
|
return message.data != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasStableIds() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mmessages.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return mmessages.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return mmessages.get(position).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View view = minflater.inflate(R.layout.mailbox_message_item, null);
|
||||||
|
|
||||||
|
Message message = (Message) getItem(position);
|
||||||
|
|
||||||
|
TextView subject = (TextView) view.findViewById(R.id.subject);
|
||||||
|
TextView email = (TextView) view.findViewById(R.id.email_address);
|
||||||
|
ImageView encrypted = (ImageView) view.findViewById(R.id.ic_encrypted);
|
||||||
|
|
||||||
|
subject.setText(message.subject);
|
||||||
|
email.setText(message.fromAddress);
|
||||||
|
if (message.data != null) {
|
||||||
|
encrypted.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
encrypted.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
394
src/org/thialfihar/android/apg/MainActivity.java
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import org.thialfihar.android.apg.provider.Accounts;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.SQLException;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.text.util.Linkify;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CursorAdapter;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
private static final int DIALOG_NEW_ACCOUNT = 1;
|
||||||
|
private static final int DIALOG_ABOUT = 2;
|
||||||
|
private static final int DIALOG_CHANGE_LOG = 3;
|
||||||
|
|
||||||
|
private static final int OPTION_MENU_ADD_ACCOUNT = 1;
|
||||||
|
private static final int OPTION_MENU_ABOUT = 2;
|
||||||
|
private static final int OPTION_MENU_MANAGE_PUBLIC_KEYS = 3;
|
||||||
|
private static final int OPTION_MENU_MANAGE_SECRET_KEYS = 4;
|
||||||
|
|
||||||
|
private static final int MENU_DELETE_ACCOUNT = 1;
|
||||||
|
|
||||||
|
private static String PREF_SEEN_CHANGE_LOG = "seenChangeLogDialog" + Apg.VERSION;
|
||||||
|
|
||||||
|
private ListView mAccounts = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.main);
|
||||||
|
|
||||||
|
Button encryptMessageButton = (Button) findViewById(R.id.btn_encryptMessage);
|
||||||
|
Button decryptMessageButton = (Button) findViewById(R.id.btn_decryptMessage);
|
||||||
|
mAccounts = (ListView) findViewById(R.id.account_list);
|
||||||
|
|
||||||
|
encryptMessageButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
startEncryptMessageActivity();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
decryptMessageButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
startDecryptMessageActivity();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Cursor accountCursor = managedQuery(Accounts.CONTENT_URI, null, null, null, null);
|
||||||
|
|
||||||
|
mAccounts.setAdapter(new AccountListAdapter(this, accountCursor));
|
||||||
|
mAccounts.setOnItemClickListener(new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> arg0, View view, int index, long id) {
|
||||||
|
Cursor cursor =
|
||||||
|
managedQuery(Uri.withAppendedPath(Accounts.CONTENT_URI, "" + id), null,
|
||||||
|
null, null, null);
|
||||||
|
if (cursor != null && cursor.getCount() > 0) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
int nameIndex = cursor.getColumnIndex(Accounts.NAME);
|
||||||
|
String accountName = cursor.getString(nameIndex);
|
||||||
|
startMailListActivity(accountName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registerForContextMenu(mAccounts);
|
||||||
|
|
||||||
|
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
|
||||||
|
if (!prefs.getBoolean(PREF_SEEN_CHANGE_LOG, false)) {
|
||||||
|
showDialog(DIALOG_CHANGE_LOG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case DIALOG_NEW_ACCOUNT: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
alert.setTitle("Add Account");
|
||||||
|
alert.setMessage("Specify the Google Mail account you want to add.");
|
||||||
|
|
||||||
|
final EditText input = new EditText(this);
|
||||||
|
alert.setView(input);
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
MainActivity.this.removeDialog(DIALOG_NEW_ACCOUNT);
|
||||||
|
String accountName = "" + input.getText();
|
||||||
|
|
||||||
|
Cursor testCursor =
|
||||||
|
managedQuery(Uri.parse("content://gmail-ls/conversations/" +
|
||||||
|
accountName),
|
||||||
|
null, null, null, null);
|
||||||
|
if (testCursor == null) {
|
||||||
|
Toast.makeText(MainActivity.this,
|
||||||
|
"Error: account '" + accountName +
|
||||||
|
"' not found",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(Accounts.NAME, accountName);
|
||||||
|
try {
|
||||||
|
MainActivity.this.getContentResolver()
|
||||||
|
.insert(Accounts.CONTENT_URI,
|
||||||
|
values);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
Toast.makeText(MainActivity.this,
|
||||||
|
"Error: failed to add account '" +
|
||||||
|
accountName + "'",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
MainActivity.this.removeDialog(DIALOG_NEW_ACCOUNT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_ABOUT: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
alert.setTitle("About " + Apg.FULL_VERSION);
|
||||||
|
ScrollView scrollView = new ScrollView(this);
|
||||||
|
TextView message = new TextView(this);
|
||||||
|
|
||||||
|
SpannableString info =
|
||||||
|
new SpannableString("This is an attempt to bring OpenPGP to Android. " +
|
||||||
|
"It is far from complete, but more features are " +
|
||||||
|
"planned (see website).\n" +
|
||||||
|
"\n" +
|
||||||
|
"Feel free to send bug reports, suggestions, feature " +
|
||||||
|
"requests, feedback, photographs.\n" +
|
||||||
|
"\n" +
|
||||||
|
"mail: thi@thialfihar.org\n" +
|
||||||
|
"site: http://apg.thialfihar.org\n" +
|
||||||
|
"\n" +
|
||||||
|
"This software is provided \"as is\", without " +
|
||||||
|
"warranty of any kind.");
|
||||||
|
Linkify.addLinks(info, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);
|
||||||
|
message.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
message.setText(info);
|
||||||
|
// 5dip padding
|
||||||
|
int padding = (int) (10 * getResources().getDisplayMetrics().densityDpi / 160);
|
||||||
|
message.setPadding(padding, padding, padding, padding);
|
||||||
|
message.setTextAppearance(this, android.R.style.TextAppearance_Medium);
|
||||||
|
scrollView.addView(message);
|
||||||
|
alert.setView(scrollView);
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
MainActivity.this.removeDialog(DIALOG_ABOUT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_CHANGE_LOG: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
alert.setTitle("Changes " + Apg.FULL_VERSION);
|
||||||
|
ScrollView scrollView = new ScrollView(this);
|
||||||
|
TextView message = new TextView(this);
|
||||||
|
|
||||||
|
SpannableString info =
|
||||||
|
new SpannableString("Read the warnings!\n\n" +
|
||||||
|
"Changes:\n" +
|
||||||
|
" * create/edit keys\n" +
|
||||||
|
" * export keys\n" +
|
||||||
|
" * GUI more Android-like\n" +
|
||||||
|
" * better error handling\n" +
|
||||||
|
" * bug fixes, optimizations\n" +
|
||||||
|
" * starting with v0.8.0 APG will be open source, see website\n" +
|
||||||
|
"\n" +
|
||||||
|
"WARNING: be careful editing your existing keys, as they " +
|
||||||
|
"WILL be stripped of certificates right now.\n" +
|
||||||
|
"WARNING: key creation/editing doesn't support all " +
|
||||||
|
"GPG features yet. In particular: " +
|
||||||
|
"key cross-certification is NOT supported, so signing " +
|
||||||
|
"with those keys will get a warning when the signature is " +
|
||||||
|
"checked.\n" +
|
||||||
|
"\n" +
|
||||||
|
"I hope APG continues to be useful to you, please send " +
|
||||||
|
"bug reports, feature wishes, feedback.");
|
||||||
|
message.setText(info);
|
||||||
|
// 5dip padding
|
||||||
|
int padding = (int) (10 * getResources().getDisplayMetrics().densityDpi / 160);
|
||||||
|
message.setPadding(padding, padding, padding, padding);
|
||||||
|
message.setTextAppearance(this, android.R.style.TextAppearance_Medium);
|
||||||
|
scrollView.addView(message);
|
||||||
|
alert.setView(scrollView);
|
||||||
|
|
||||||
|
alert.setCancelable(false);
|
||||||
|
alert.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
MainActivity.this.removeDialog(DIALOG_CHANGE_LOG);
|
||||||
|
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(PREF_SEEN_CHANGE_LOG, true);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onCreateDialog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
menu.add(0, OPTION_MENU_MANAGE_PUBLIC_KEYS, 0, R.string.menu_managePublicKeys)
|
||||||
|
.setIcon(android.R.drawable.ic_menu_manage);
|
||||||
|
menu.add(0, OPTION_MENU_MANAGE_SECRET_KEYS, 1, R.string.menu_manageSecretKeys)
|
||||||
|
.setIcon(android.R.drawable.ic_menu_manage);
|
||||||
|
menu.add(1, OPTION_MENU_ADD_ACCOUNT, 2, R.string.menu_addAccount)
|
||||||
|
.setIcon(android.R.drawable.ic_menu_add);
|
||||||
|
menu.add(1, OPTION_MENU_ABOUT, 3, R.string.menu_about)
|
||||||
|
.setIcon(android.R.drawable.ic_menu_info_details);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case OPTION_MENU_ADD_ACCOUNT: {
|
||||||
|
showDialog(DIALOG_NEW_ACCOUNT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_MENU_ABOUT: {
|
||||||
|
showDialog(DIALOG_ABOUT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_MENU_MANAGE_PUBLIC_KEYS: {
|
||||||
|
startPublicKeyManager();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_MENU_MANAGE_SECRET_KEYS: {
|
||||||
|
startSecretKeyManager();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||||
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
|
|
||||||
|
TextView nameTextView = (TextView) v.findViewById(R.id.account_name);
|
||||||
|
if (nameTextView != null) {
|
||||||
|
menu.setHeaderTitle(nameTextView.getText());
|
||||||
|
menu.add(0, MENU_DELETE_ACCOUNT, 0, "Delete Account");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onContextItemSelected(MenuItem menuItem) {
|
||||||
|
AdapterView.AdapterContextMenuInfo info =
|
||||||
|
(AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||||
|
|
||||||
|
switch (menuItem.getItemId()) {
|
||||||
|
case MENU_DELETE_ACCOUNT: {
|
||||||
|
Uri uri = Uri.withAppendedPath(Accounts.CONTENT_URI, "" + info.id);
|
||||||
|
this.getContentResolver().delete(uri, null, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return super.onContextItemSelected(menuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startPublicKeyManager() {
|
||||||
|
startActivity(new Intent(this, PublicKeyListActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startSecretKeyManager() {
|
||||||
|
startActivity(new Intent(this, SecretKeyListActivity.class));
|
||||||
|
//startActivity(new Intent(this, EditKeyActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startEncryptMessageActivity() {
|
||||||
|
startActivity(new Intent(this, EncryptMessageActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startDecryptMessageActivity() {
|
||||||
|
startActivity(new Intent(this, DecryptMessageActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startMailListActivity(String account) {
|
||||||
|
startActivity(new Intent(this, MailListActivity.class).putExtra("account", account));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AccountListAdapter extends CursorAdapter {
|
||||||
|
private LayoutInflater minflater;
|
||||||
|
|
||||||
|
public AccountListAdapter(Context context, Cursor cursor) {
|
||||||
|
super(context, cursor);
|
||||||
|
minflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return super.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
|
return minflater.inflate(R.layout.account_item, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
TextView nameTextView = (TextView) view.findViewById(R.id.account_name);
|
||||||
|
int nameIndex = cursor.getColumnIndex(Accounts.NAME);
|
||||||
|
final String account = cursor.getString(nameIndex);
|
||||||
|
nameTextView.setText(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(int position) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/org/thialfihar/android/apg/ProgressDialogUpdater.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
public interface ProgressDialogUpdater {
|
||||||
|
void setProgress(String message, int current, int total);
|
||||||
|
void setProgress(int current, int total);
|
||||||
|
}
|
660
src/org/thialfihar/android/apg/PublicKeyListActivity.java
Normal file
@ -0,0 +1,660 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.bouncycastle2.openpgp.PGPException;
|
||||||
|
import org.bouncycastle2.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.thialfihar.android.apg.utils.IterableIterator;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ExpandableListActivity;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
|
import android.view.View.OnKeyListener;
|
||||||
|
import android.widget.BaseExpandableListAdapter;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ExpandableListView;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||||
|
|
||||||
|
public class PublicKeyListActivity extends ExpandableListActivity
|
||||||
|
implements Runnable, ProgressDialogUpdater {
|
||||||
|
static final int MENU_DELETE = 1;
|
||||||
|
static final int MENU_EXPORT = 2;
|
||||||
|
|
||||||
|
static final int OPTION_MENU_IMPORT_KEYS = 1;
|
||||||
|
static final int OPTION_MENU_EXPORT_KEYS = 2;
|
||||||
|
|
||||||
|
static final int MESSAGE_PROGRESS_UPDATE = 1;
|
||||||
|
static final int MESSAGE_IMPORT_DONE = 2;
|
||||||
|
static final int MESSAGE_EXPORT_DONE = 3;
|
||||||
|
|
||||||
|
static final int DIALOG_DELETE_KEY = 1;
|
||||||
|
static final int DIALOG_IMPORT_KEYS = 2;
|
||||||
|
static final int DIALOG_IMPORTING = 3;
|
||||||
|
static final int DIALOG_EXPORT_KEYS = 4;
|
||||||
|
static final int DIALOG_EXPORTING = 5;
|
||||||
|
static final int DIALOG_EXPORT_KEY = 6;
|
||||||
|
|
||||||
|
static final int TASK_IMPORT = 1;
|
||||||
|
static final int TASK_EXPORT = 2;
|
||||||
|
|
||||||
|
protected int mSelectedItem = -1;
|
||||||
|
protected String mImportFilename = null;
|
||||||
|
protected String mExportFilename = null;
|
||||||
|
protected int mTask = 0;
|
||||||
|
|
||||||
|
private ProgressDialog mProgressDialog = null;
|
||||||
|
private Thread mRunningThread = null;
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
Bundle data = msg.getData();
|
||||||
|
if (data != null) {
|
||||||
|
int type = data.getInt("type");
|
||||||
|
switch (type) {
|
||||||
|
case MESSAGE_PROGRESS_UPDATE: {
|
||||||
|
String message = data.getString("message");
|
||||||
|
if (mProgressDialog != null) {
|
||||||
|
if (message != null) {
|
||||||
|
mProgressDialog.setMessage(message);
|
||||||
|
}
|
||||||
|
mProgressDialog.setMax(data.getInt("max"));
|
||||||
|
mProgressDialog.setProgress(data.getInt("progress"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE_IMPORT_DONE: {
|
||||||
|
removeDialog(DIALOG_IMPORTING);
|
||||||
|
mProgressDialog = null;
|
||||||
|
|
||||||
|
String error = data.getString("error");
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(PublicKeyListActivity.this,
|
||||||
|
"Error: " + data.getString("error"),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
int added = data.getInt("added");
|
||||||
|
int updated = data.getInt("updated");
|
||||||
|
String message;
|
||||||
|
if (added > 0 && updated > 0) {
|
||||||
|
message = "Succssfully added " + added + " keys and updated " +
|
||||||
|
updated + " keys.";
|
||||||
|
} else if (added > 0) {
|
||||||
|
message = "Succssfully added " + added + " keys.";
|
||||||
|
} else if (updated > 0) {
|
||||||
|
message = "Succssfully updated " + updated + " keys.";
|
||||||
|
} else {
|
||||||
|
message = "No keys added or updated.";
|
||||||
|
}
|
||||||
|
Toast.makeText(PublicKeyListActivity.this, message,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
refreshList();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE_EXPORT_DONE: {
|
||||||
|
removeDialog(DIALOG_EXPORTING);
|
||||||
|
mProgressDialog = null;
|
||||||
|
|
||||||
|
String error = data.getString("error");
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(PublicKeyListActivity.this,
|
||||||
|
"Error: " + data.getString("error"),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
int exported = data.getInt("exported");
|
||||||
|
String message;
|
||||||
|
if (exported == 1) {
|
||||||
|
message = "Succssfully exported 1 key.";
|
||||||
|
} else if (exported > 0) {
|
||||||
|
message = "Succssfully exported " + exported + " keys.";
|
||||||
|
} else{
|
||||||
|
message = "No keys exported.";
|
||||||
|
}
|
||||||
|
Toast.makeText(PublicKeyListActivity.this, message,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public void setProgress(int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(String message, int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putString("message", message);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Apg.initialize(this);
|
||||||
|
|
||||||
|
setListAdapter(new PublicKeyListAdapter(this));
|
||||||
|
registerForContextMenu(getExpandableListView());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
menu.add(0, OPTION_MENU_IMPORT_KEYS, 0, "Import Keys")
|
||||||
|
.setIcon(android.R.drawable.ic_menu_add);
|
||||||
|
menu.add(0, OPTION_MENU_EXPORT_KEYS, 1, "Export Keys")
|
||||||
|
.setIcon(android.R.drawable.ic_menu_save);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case OPTION_MENU_IMPORT_KEYS: {
|
||||||
|
showDialog(DIALOG_IMPORT_KEYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_MENU_EXPORT_KEYS: {
|
||||||
|
showDialog(DIALOG_EXPORT_KEYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||||
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
|
ExpandableListView.ExpandableListContextMenuInfo info =
|
||||||
|
(ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
|
||||||
|
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||||
|
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
|
||||||
|
|
||||||
|
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||||
|
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(groupPosition);
|
||||||
|
String userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey(keyRing));
|
||||||
|
menu.setHeaderTitle(userId);
|
||||||
|
menu.add(0, MENU_EXPORT, 0, "Export Key");
|
||||||
|
menu.add(0, MENU_DELETE, 1, "Delete Key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onContextItemSelected(MenuItem menuItem) {
|
||||||
|
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
|
||||||
|
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||||
|
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
|
||||||
|
|
||||||
|
if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||||
|
return super.onContextItemSelected(menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (menuItem.getItemId()) {
|
||||||
|
case MENU_EXPORT: {
|
||||||
|
mSelectedItem = groupPosition;
|
||||||
|
showDialog(DIALOG_EXPORT_KEY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MENU_DELETE: {
|
||||||
|
mSelectedItem = groupPosition;
|
||||||
|
showDialog(DIALOG_DELETE_KEY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return super.onContextItemSelected(menuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
boolean singleKeyExport = false;
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
case DIALOG_DELETE_KEY: {
|
||||||
|
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(mSelectedItem);
|
||||||
|
String userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey(keyRing));
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle("Warning ");
|
||||||
|
builder.setMessage("Do you really want to delete the key '" + userId + "'?\n" +
|
||||||
|
"You can't undo this!");
|
||||||
|
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||||
|
builder.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
deleteKey(mSelectedItem);
|
||||||
|
mSelectedItem = -1;
|
||||||
|
removeDialog(DIALOG_DELETE_KEY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
mSelectedItem = -1;
|
||||||
|
removeDialog(DIALOG_DELETE_KEY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_IMPORT_KEYS: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
alert.setTitle("Import Keys");
|
||||||
|
alert.setMessage("Please specify which file to import from.");
|
||||||
|
|
||||||
|
final EditText input = new EditText(this);
|
||||||
|
// TODO: default file?
|
||||||
|
input.setText(Environment.getExternalStorageDirectory() + "/pubring.gpg");
|
||||||
|
input.setOnKeyListener(new OnKeyListener() {
|
||||||
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
|
// TODO: this doesn't actually work yet
|
||||||
|
// If the event is a key-down event on the "enter"
|
||||||
|
// button
|
||||||
|
if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
|
||||||
|
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||||
|
try {
|
||||||
|
((AlertDialog) v.getParent())
|
||||||
|
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
|
.performClick();
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// don't do anything if we're not in that dialog
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alert.setView(input);
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(DIALOG_IMPORT_KEYS);
|
||||||
|
mImportFilename = input.getText().toString();
|
||||||
|
importKeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(DIALOG_IMPORT_KEYS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_EXPORT_KEY: {
|
||||||
|
singleKeyExport = true;
|
||||||
|
// break intentionally omitted, to use the DIALOG_EXPORT_KEYS dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_EXPORT_KEYS: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
if (singleKeyExport) {
|
||||||
|
alert.setTitle("Export Key");
|
||||||
|
} else {
|
||||||
|
alert.setTitle("Export Keys");
|
||||||
|
mSelectedItem = -1;
|
||||||
|
}
|
||||||
|
final int thisDialogId = (singleKeyExport ? DIALOG_DELETE_KEY : DIALOG_EXPORT_KEYS);
|
||||||
|
alert.setMessage("Please specify which file to export to.\n" +
|
||||||
|
"WARNING! File will be overwritten if it exists.");
|
||||||
|
|
||||||
|
final EditText input = new EditText(this);
|
||||||
|
// TODO: default file?
|
||||||
|
input.setText(Environment.getExternalStorageDirectory() + "/pubexport.asc");
|
||||||
|
alert.setView(input);
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(thisDialogId);
|
||||||
|
mExportFilename = input.getText().toString();
|
||||||
|
exportKeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(thisDialogId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_IMPORTING: {
|
||||||
|
mProgressDialog = new ProgressDialog(this);
|
||||||
|
mProgressDialog.setMessage("importing...");
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
return mProgressDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_EXPORTING: {
|
||||||
|
mProgressDialog = new ProgressDialog(this);
|
||||||
|
mProgressDialog.setMessage("exporting...");
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
return mProgressDialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onCreateDialog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importKeys() {
|
||||||
|
showDialog(DIALOG_IMPORTING);
|
||||||
|
mTask = TASK_IMPORT;
|
||||||
|
mRunningThread = new Thread(this);
|
||||||
|
mRunningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportKeys() {
|
||||||
|
showDialog(DIALOG_EXPORTING);
|
||||||
|
mTask = TASK_EXPORT;
|
||||||
|
mRunningThread = new Thread(this);
|
||||||
|
mRunningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
String error = null;
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
Message msg = new Message();
|
||||||
|
|
||||||
|
String filename = null;
|
||||||
|
if (mTask == TASK_IMPORT) {
|
||||||
|
filename = mImportFilename;
|
||||||
|
} else {
|
||||||
|
filename = mExportFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (mTask == TASK_IMPORT) {
|
||||||
|
data = Apg.importKeyRings(this, Apg.TYPE_PUBLIC, filename, this);
|
||||||
|
} else {
|
||||||
|
Vector<Object> keys = new Vector<Object>();
|
||||||
|
if (mSelectedItem == -1) {
|
||||||
|
for (PGPPublicKeyRing key : Apg.getPublicKeyRings()) {
|
||||||
|
keys.add(key);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keys.add(Apg.getPublicKeyRings().get(mSelectedItem));
|
||||||
|
}
|
||||||
|
data = Apg.exportKeyRings(this, keys, filename, this);
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
error = "file '" + filename + "' not found";
|
||||||
|
} catch (IOException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (PGPException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (Apg.GeneralException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mTask == TASK_IMPORT) {
|
||||||
|
data.putInt("type", MESSAGE_IMPORT_DONE);
|
||||||
|
} else {
|
||||||
|
data.putInt("type", MESSAGE_EXPORT_DONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
data.putString("error", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteKey(int index) {
|
||||||
|
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(index);
|
||||||
|
Apg.deleteKey(this, keyRing);
|
||||||
|
refreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshList() {
|
||||||
|
((PublicKeyListAdapter) getExpandableListAdapter()).notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PublicKeyListAdapter extends BaseExpandableListAdapter {
|
||||||
|
private LayoutInflater mInflater;
|
||||||
|
|
||||||
|
private class KeyChild {
|
||||||
|
public static final int KEY = 0;
|
||||||
|
public static final int USER_ID = 1;
|
||||||
|
|
||||||
|
public int type;
|
||||||
|
public PGPPublicKey key;
|
||||||
|
public String userId;
|
||||||
|
|
||||||
|
public KeyChild(PGPPublicKey key) {
|
||||||
|
type = KEY;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyChild(String userId) {
|
||||||
|
type = USER_ID;
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicKeyListAdapter(Context context) {
|
||||||
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector<KeyChild> getChildrenOfKeyRing(PGPPublicKeyRing keyRing) {
|
||||||
|
Vector<KeyChild> children = new Vector<KeyChild>();
|
||||||
|
PGPPublicKey masterKey = null;
|
||||||
|
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
||||||
|
children.add(new KeyChild(key));
|
||||||
|
if (key.isMasterKey()) {
|
||||||
|
masterKey = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (masterKey != null) {
|
||||||
|
boolean isFirst = true;
|
||||||
|
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||||
|
if (isFirst) {
|
||||||
|
// ignore first, it's in the group already
|
||||||
|
isFirst = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
children.add(new KeyChild(userId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasStableIds() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChildSelectable(int groupPosition, int childPosition) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGroupCount() {
|
||||||
|
return Apg.getPublicKeyRings().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getChild(int groupPosition, int childPosition) {
|
||||||
|
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(groupPosition);
|
||||||
|
Vector<KeyChild> children = getChildrenOfKeyRing(keyRing);
|
||||||
|
|
||||||
|
KeyChild child = children.get(childPosition);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getChildId(int groupPosition, int childPosition) {
|
||||||
|
return childPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChildrenCount(int groupPosition) {
|
||||||
|
return getChildrenOfKeyRing(Apg.getPublicKeyRings().get(groupPosition)).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getGroup(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getGroupId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
|
||||||
|
ViewGroup parent) {
|
||||||
|
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(groupPosition);
|
||||||
|
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
||||||
|
View view;
|
||||||
|
if (!key.isMasterKey()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
view = mInflater.inflate(R.layout.key_list_group_item, null);
|
||||||
|
view.setBackgroundResource(android.R.drawable.list_selector_background);
|
||||||
|
|
||||||
|
TextView mainUserId = (TextView) view.findViewById(R.id.main_user_id);
|
||||||
|
mainUserId.setText("");
|
||||||
|
TextView mainUserIdRest = (TextView) view.findViewById(R.id.main_user_id_rest);
|
||||||
|
mainUserIdRest.setText("");
|
||||||
|
|
||||||
|
String userId = Apg.getMainUserId(key);
|
||||||
|
if (userId != null) {
|
||||||
|
String chunks[] = userId.split(" <", 2);
|
||||||
|
userId = chunks[0];
|
||||||
|
if (chunks.length > 1) {
|
||||||
|
mainUserIdRest.setText("<" + chunks[1]);
|
||||||
|
}
|
||||||
|
mainUserId.setText(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainUserId.getText().length() == 0) {
|
||||||
|
mainUserId.setText(R.string.unknown_user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainUserIdRest.getText().length() == 0) {
|
||||||
|
mainUserIdRest.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getChildView(int groupPosition, int childPosition,
|
||||||
|
boolean isLastChild, View convertView,
|
||||||
|
ViewGroup parent) {
|
||||||
|
PGPPublicKeyRing keyRing = Apg.getPublicKeyRings().get(groupPosition);
|
||||||
|
Vector<KeyChild> children = getChildrenOfKeyRing(keyRing);
|
||||||
|
|
||||||
|
KeyChild child = children.get(childPosition);
|
||||||
|
View view = null;
|
||||||
|
switch (child.type) {
|
||||||
|
case KeyChild.KEY: {
|
||||||
|
PGPPublicKey key = child.key;
|
||||||
|
if (key.isMasterKey()) {
|
||||||
|
view = mInflater.inflate(R.layout.key_list_child_item_master_key, null);
|
||||||
|
} else {
|
||||||
|
view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView keyId = (TextView) view.findViewById(R.id.key_id);
|
||||||
|
String keyIdStr = Long.toHexString(key.getKeyID() & 0xffffffffL);
|
||||||
|
while (keyIdStr.length() < 8) {
|
||||||
|
keyIdStr = "0" + keyIdStr;
|
||||||
|
}
|
||||||
|
keyId.setText(keyIdStr);
|
||||||
|
TextView keyDetails = (TextView) view.findViewById(R.id.key_details);
|
||||||
|
String algorithmStr = Apg.getAlgorithmInfo(key);
|
||||||
|
keyDetails.setText("(" + algorithmStr + ")");
|
||||||
|
|
||||||
|
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encrypt_key);
|
||||||
|
if (!Apg.isEncryptionKey(key)) {
|
||||||
|
encryptIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_sign_key);
|
||||||
|
if (!Apg.isSigningKey(key)) {
|
||||||
|
signIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KeyChild.USER_ID: {
|
||||||
|
view = mInflater.inflate(R.layout.key_list_child_item_user_id, null);
|
||||||
|
TextView userId = (TextView) view.findViewById(R.id.user_id);
|
||||||
|
userId.setText(child.userId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
758
src/org/thialfihar/android/apg/SecretKeyListActivity.java
Normal file
@ -0,0 +1,758 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.bouncycastle2.openpgp.PGPException;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.thialfihar.android.apg.utils.IterableIterator;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ExpandableListActivity;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
|
import android.view.View.OnKeyListener;
|
||||||
|
import android.widget.BaseExpandableListAdapter;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ExpandableListView;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||||
|
import android.widget.ExpandableListView.OnChildClickListener;
|
||||||
|
|
||||||
|
public class SecretKeyListActivity extends ExpandableListActivity
|
||||||
|
implements Runnable, ProgressDialogUpdater, OnChildClickListener,
|
||||||
|
AskForSecretKeyPassPhrase.PassPhraseCallbackInterface {
|
||||||
|
static final int CREATE_SECRET_KEY = 1;
|
||||||
|
static final int EDIT_SECRET_KEY = 2;
|
||||||
|
|
||||||
|
static final int MENU_EDIT = 1;
|
||||||
|
static final int MENU_EXPORT = 2;
|
||||||
|
static final int MENU_DELETE = 3;
|
||||||
|
|
||||||
|
static final int OPTION_MENU_IMPORT_KEYS = 1;
|
||||||
|
static final int OPTION_MENU_EXPORT_KEYS = 2;
|
||||||
|
static final int OPTION_MENU_CREATE_KEY = 3;
|
||||||
|
|
||||||
|
static final int MESSAGE_PROGRESS_UPDATE = 1;
|
||||||
|
static final int MESSAGE_DONE = 2;
|
||||||
|
static final int MESSAGE_IMPORT_DONE = 2;
|
||||||
|
static final int MESSAGE_EXPORT_DONE = 3;
|
||||||
|
|
||||||
|
static final int DIALOG_DELETE_KEY = 1;
|
||||||
|
static final int DIALOG_IMPORT_KEYS = 2;
|
||||||
|
static final int DIALOG_IMPORTING = 3;
|
||||||
|
static final int DIALOG_EXPORT_KEYS = 4;
|
||||||
|
static final int DIALOG_EXPORTING = 5;
|
||||||
|
static final int DIALOG_EXPORT_KEY = 6;
|
||||||
|
|
||||||
|
static final int TASK_IMPORT = 1;
|
||||||
|
static final int TASK_EXPORT = 2;
|
||||||
|
|
||||||
|
protected int mSelectedItem = -1;
|
||||||
|
protected String mImportFilename = null;
|
||||||
|
protected String mExportFilename = null;
|
||||||
|
protected int mTask = 0;
|
||||||
|
|
||||||
|
private ProgressDialog mProgressDialog = null;
|
||||||
|
private Thread mRunningThread = null;
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
Bundle data = msg.getData();
|
||||||
|
if (data != null) {
|
||||||
|
int type = data.getInt("type");
|
||||||
|
switch (type) {
|
||||||
|
case MESSAGE_PROGRESS_UPDATE: {
|
||||||
|
String message = data.getString("message");
|
||||||
|
if (mProgressDialog != null) {
|
||||||
|
if (message != null) {
|
||||||
|
mProgressDialog.setMessage(message);
|
||||||
|
}
|
||||||
|
mProgressDialog.setMax(data.getInt("max"));
|
||||||
|
mProgressDialog.setProgress(data.getInt("progress"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE_IMPORT_DONE: {
|
||||||
|
removeDialog(DIALOG_IMPORTING);
|
||||||
|
mProgressDialog = null;
|
||||||
|
|
||||||
|
String error = data.getString("error");
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(SecretKeyListActivity.this,
|
||||||
|
"Error: " + data.getString("error"),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
int added = data.getInt("added");
|
||||||
|
int updated = data.getInt("updated");
|
||||||
|
String message;
|
||||||
|
if (added > 0 && updated > 0) {
|
||||||
|
message = "Succssfully added " + added + " keys and updated " +
|
||||||
|
updated + " keys.";
|
||||||
|
} else if (added > 0) {
|
||||||
|
message = "Succssfully added " + added + " keys.";
|
||||||
|
} else if (updated > 0) {
|
||||||
|
message = "Succssfully updated " + updated + " keys.";
|
||||||
|
} else {
|
||||||
|
message = "No keys added or updated.";
|
||||||
|
}
|
||||||
|
Toast.makeText(SecretKeyListActivity.this, message,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
refreshList();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE_EXPORT_DONE: {
|
||||||
|
removeDialog(DIALOG_EXPORTING);
|
||||||
|
mProgressDialog = null;
|
||||||
|
|
||||||
|
String error = data.getString("error");
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(SecretKeyListActivity.this,
|
||||||
|
"Error: " + data.getString("error"),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
int exported = data.getInt("exported");
|
||||||
|
String message;
|
||||||
|
if (exported == 1) {
|
||||||
|
message = "Succssfully exported 1 key.";
|
||||||
|
} else if (exported > 0) {
|
||||||
|
message = "Succssfully exported " + exported + " keys.";
|
||||||
|
} else{
|
||||||
|
message = "No keys exported.";
|
||||||
|
}
|
||||||
|
Toast.makeText(SecretKeyListActivity.this, message,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public void setProgress(int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(String message, int progress, int max) {
|
||||||
|
Message msg = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putInt("type", MESSAGE_PROGRESS_UPDATE);
|
||||||
|
data.putString("message", message);
|
||||||
|
data.putInt("progress", progress);
|
||||||
|
data.putInt("max", max);
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Apg.initialize(this);
|
||||||
|
|
||||||
|
setListAdapter(new SecretKeyListAdapter(this));
|
||||||
|
registerForContextMenu(getExpandableListView());
|
||||||
|
getExpandableListView().setOnChildClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
menu.add(0, OPTION_MENU_IMPORT_KEYS, 0, "Import Keys")
|
||||||
|
.setIcon(android.R.drawable.ic_menu_add);
|
||||||
|
menu.add(0, OPTION_MENU_EXPORT_KEYS, 1, "Export Keys")
|
||||||
|
.setIcon(android.R.drawable.ic_menu_save);
|
||||||
|
menu.add(1, OPTION_MENU_CREATE_KEY, 2, "Create Key")
|
||||||
|
.setIcon(android.R.drawable.ic_menu_add);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case OPTION_MENU_IMPORT_KEYS: {
|
||||||
|
showDialog(DIALOG_IMPORT_KEYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_MENU_EXPORT_KEYS: {
|
||||||
|
showDialog(DIALOG_EXPORT_KEYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_MENU_CREATE_KEY: {
|
||||||
|
createKey();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||||
|
ContextMenuInfo menuInfo) {
|
||||||
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
|
ExpandableListView.ExpandableListContextMenuInfo info =
|
||||||
|
(ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
|
||||||
|
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||||
|
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
|
||||||
|
|
||||||
|
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(groupPosition);
|
||||||
|
String userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey(keyRing));
|
||||||
|
menu.setHeaderTitle(userId);
|
||||||
|
menu.add(0, MENU_EDIT, 0, "Edit Key");
|
||||||
|
menu.add(0, MENU_EXPORT, 1, "Export Key");
|
||||||
|
menu.add(0, MENU_DELETE, 2, "Delete Key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onContextItemSelected(MenuItem menuItem) {
|
||||||
|
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
|
||||||
|
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||||
|
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
|
||||||
|
|
||||||
|
if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||||
|
return super.onContextItemSelected(menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (menuItem.getItemId()) {
|
||||||
|
case MENU_EDIT: {
|
||||||
|
mSelectedItem = groupPosition;
|
||||||
|
showDialog(AskForSecretKeyPassPhrase.DIALOG_PASS_PHRASE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MENU_EXPORT: {
|
||||||
|
mSelectedItem = groupPosition;
|
||||||
|
showDialog(DIALOG_EXPORT_KEY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MENU_DELETE: {
|
||||||
|
mSelectedItem = groupPosition;
|
||||||
|
showDialog(DIALOG_DELETE_KEY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return super.onContextItemSelected(menuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
|
||||||
|
int childPosition, long id) {
|
||||||
|
mSelectedItem = groupPosition;
|
||||||
|
showDialog(AskForSecretKeyPassPhrase.DIALOG_PASS_PHRASE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
boolean singleKeyExport = false;
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
case DIALOG_DELETE_KEY: {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(mSelectedItem);
|
||||||
|
|
||||||
|
String userId = Apg.getMainUserIdSafe(this, Apg.getMasterKey(keyRing));
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle("Warning ");
|
||||||
|
builder.setMessage("Do you really want to delete the key '" + userId + "'?\n" +
|
||||||
|
"You can't undo this!");
|
||||||
|
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||||
|
builder.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
deleteKey(mSelectedItem);
|
||||||
|
mSelectedItem = -1;
|
||||||
|
removeDialog(DIALOG_DELETE_KEY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
mSelectedItem = -1;
|
||||||
|
removeDialog(DIALOG_DELETE_KEY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_IMPORT_KEYS: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
alert.setTitle("Import Keys");
|
||||||
|
alert.setMessage("Please specify which file to import from.");
|
||||||
|
|
||||||
|
final EditText input = new EditText(this);
|
||||||
|
// TODO: default file?
|
||||||
|
input.setText(Environment.getExternalStorageDirectory() + "/secring.gpg");
|
||||||
|
input.setOnKeyListener(new OnKeyListener() {
|
||||||
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
|
// TODO: this doesn't actually work yet
|
||||||
|
// If the event is a key-down event on the "enter"
|
||||||
|
// button
|
||||||
|
if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
|
||||||
|
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||||
|
try {
|
||||||
|
((AlertDialog) v.getParent())
|
||||||
|
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
|
.performClick();
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// don't do anything if we're not in that dialog
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alert.setView(input);
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(DIALOG_IMPORT_KEYS);
|
||||||
|
mImportFilename = input.getText().toString();
|
||||||
|
importKeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(DIALOG_IMPORT_KEYS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_EXPORT_KEY: {
|
||||||
|
singleKeyExport = true;
|
||||||
|
// break intentionally omitted, to use the DIALOG_EXPORT_KEYS dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_EXPORT_KEYS: {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
if (singleKeyExport) {
|
||||||
|
alert.setTitle("Export Key");
|
||||||
|
} else {
|
||||||
|
alert.setTitle("Export Keys");
|
||||||
|
mSelectedItem = -1;
|
||||||
|
}
|
||||||
|
final int thisDialogId = (singleKeyExport ? DIALOG_DELETE_KEY : DIALOG_EXPORT_KEYS);
|
||||||
|
alert.setMessage("Please specify which file to export to.\n" +
|
||||||
|
"WARNING! You are about to export a SECRET key.\n" +
|
||||||
|
"WARNING! File will be overwritten if it exists.");
|
||||||
|
|
||||||
|
final EditText input = new EditText(this);
|
||||||
|
// TODO: default file?
|
||||||
|
input.setText(Environment.getExternalStorageDirectory() + "/secexport.asc");
|
||||||
|
input.setOnKeyListener(new OnKeyListener() {
|
||||||
|
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||||
|
// TODO: this doesn't actually work yet
|
||||||
|
// If the event is a key-down event on the "enter"
|
||||||
|
// button
|
||||||
|
if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
|
||||||
|
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||||
|
try {
|
||||||
|
((AlertDialog) v.getParent())
|
||||||
|
.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
|
.performClick();
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// don't do anything if we're not in that dialog
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alert.setView(input);
|
||||||
|
|
||||||
|
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(thisDialogId);
|
||||||
|
mExportFilename = input.getText().toString();
|
||||||
|
exportKeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
removeDialog(thisDialogId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return alert.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_IMPORTING: {
|
||||||
|
mProgressDialog = new ProgressDialog(this);
|
||||||
|
mProgressDialog.setMessage("importing...");
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
return mProgressDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DIALOG_EXPORTING: {
|
||||||
|
mProgressDialog = new ProgressDialog(this);
|
||||||
|
mProgressDialog.setMessage("exporting...");
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
return mProgressDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AskForSecretKeyPassPhrase.DIALOG_PASS_PHRASE: {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(mSelectedItem);
|
||||||
|
long keyId = keyRing.getSecretKey().getKeyID();
|
||||||
|
return AskForSecretKeyPassPhrase.createDialog(this, keyId, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onCreateDialog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void passPhraseCallback(String passPhrase) {
|
||||||
|
Apg.setPassPhrase(passPhrase);
|
||||||
|
editKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createKey() {
|
||||||
|
Intent intent = new Intent(this, EditKeyActivity.class);
|
||||||
|
startActivityForResult(intent, CREATE_SECRET_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editKey() {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(mSelectedItem);
|
||||||
|
long keyId = keyRing.getSecretKey().getKeyID();
|
||||||
|
Intent intent = new Intent(this, EditKeyActivity.class);
|
||||||
|
intent.putExtra("keyId", keyId);
|
||||||
|
startActivityForResult(intent, EDIT_SECRET_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case CREATE_SECRET_KEY: // intentionally no break
|
||||||
|
case EDIT_SECRET_KEY: {
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
refreshList();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importKeys() {
|
||||||
|
showDialog(DIALOG_IMPORTING);
|
||||||
|
mTask = TASK_IMPORT;
|
||||||
|
mRunningThread = new Thread(this);
|
||||||
|
mRunningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportKeys() {
|
||||||
|
showDialog(DIALOG_EXPORTING);
|
||||||
|
mTask = TASK_EXPORT;
|
||||||
|
mRunningThread = new Thread(this);
|
||||||
|
mRunningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
String error = null;
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
Message msg = new Message();
|
||||||
|
|
||||||
|
String filename = null;
|
||||||
|
if (mTask == TASK_IMPORT) {
|
||||||
|
filename = mImportFilename;
|
||||||
|
} else {
|
||||||
|
filename = mExportFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (mTask == TASK_IMPORT) {
|
||||||
|
data = Apg.importKeyRings(this, Apg.TYPE_SECRET, filename, this);
|
||||||
|
} else {
|
||||||
|
Vector<Object> keys = new Vector<Object>();
|
||||||
|
if (mSelectedItem == -1) {
|
||||||
|
for (PGPSecretKeyRing key : Apg.getSecretKeyRings()) {
|
||||||
|
keys.add(key);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keys.add(Apg.getSecretKeyRings().get(mSelectedItem));
|
||||||
|
}
|
||||||
|
data = Apg.exportKeyRings(this, keys, filename, this);
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
error = "file '" + filename + "' not found";
|
||||||
|
} catch (IOException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (PGPException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (Apg.GeneralException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mTask == TASK_IMPORT) {
|
||||||
|
data.putInt("type", MESSAGE_IMPORT_DONE);
|
||||||
|
} else {
|
||||||
|
data.putInt("type", MESSAGE_EXPORT_DONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
data.putString("error", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.setData(data);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteKey(int index) {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(index);
|
||||||
|
Apg.deleteKey(this, keyRing);
|
||||||
|
refreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshList() {
|
||||||
|
((SecretKeyListAdapter) getExpandableListAdapter())
|
||||||
|
.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SecretKeyListAdapter extends BaseExpandableListAdapter {
|
||||||
|
private LayoutInflater mInflater;
|
||||||
|
|
||||||
|
private class KeyChild {
|
||||||
|
static final int KEY = 0;
|
||||||
|
static final int USER_ID = 1;
|
||||||
|
|
||||||
|
public int type;
|
||||||
|
public PGPSecretKey key;
|
||||||
|
public String userId;
|
||||||
|
|
||||||
|
public KeyChild(PGPSecretKey key) {
|
||||||
|
type = KEY;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyChild(String userId) {
|
||||||
|
type = USER_ID;
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecretKeyListAdapter(Context context) {
|
||||||
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector<KeyChild> getChildrenOfKeyRing(PGPSecretKeyRing keyRing) {
|
||||||
|
Vector<KeyChild> children = new Vector<KeyChild>();
|
||||||
|
PGPSecretKey masterKey = null;
|
||||||
|
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||||
|
children.add(new KeyChild(key));
|
||||||
|
if (key.isMasterKey()) {
|
||||||
|
masterKey = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (masterKey != null) {
|
||||||
|
boolean isFirst = true;
|
||||||
|
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||||
|
if (isFirst) {
|
||||||
|
// ignore first, it's in the group already
|
||||||
|
isFirst = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
children.add(new KeyChild(userId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasStableIds() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChildSelectable(int groupPosition, int childPosition) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGroupCount() {
|
||||||
|
return Apg.getSecretKeyRings().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getChild(int groupPosition, int childPosition) {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(groupPosition);
|
||||||
|
Vector<KeyChild> children = getChildrenOfKeyRing(keyRing);
|
||||||
|
KeyChild child = children.get(childPosition);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getChildId(int groupPosition, int childPosition) {
|
||||||
|
return childPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChildrenCount(int groupPosition) {
|
||||||
|
return getChildrenOfKeyRing(Apg.getSecretKeyRings().get(groupPosition)).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getGroup(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getGroupId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getGroupView(int groupPosition, boolean isExpanded,
|
||||||
|
View convertView, ViewGroup parent) {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(groupPosition);
|
||||||
|
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||||
|
View view;
|
||||||
|
if (!key.isMasterKey()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
view = mInflater.inflate(R.layout.key_list_group_item, null);
|
||||||
|
view.setBackgroundResource(android.R.drawable.list_selector_background);
|
||||||
|
|
||||||
|
TextView mainUserId = (TextView) view.findViewById(R.id.main_user_id);
|
||||||
|
mainUserId.setText("");
|
||||||
|
TextView mainUserIdRest = (TextView) view.findViewById(R.id.main_user_id_rest);
|
||||||
|
mainUserIdRest.setText("");
|
||||||
|
|
||||||
|
String userId = Apg.getMainUserId(key);
|
||||||
|
if (userId != null) {
|
||||||
|
String chunks[] = userId.split(" <", 2);
|
||||||
|
userId = chunks[0];
|
||||||
|
if (chunks.length > 1) {
|
||||||
|
mainUserIdRest.setText("<" + chunks[1]);
|
||||||
|
}
|
||||||
|
mainUserId.setText(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainUserId.getText().length() == 0) {
|
||||||
|
mainUserId.setText(R.string.unknown_user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainUserIdRest.getText().length() == 0) {
|
||||||
|
mainUserIdRest.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getChildView(int groupPosition, int childPosition,
|
||||||
|
boolean isLastChild, View convertView,
|
||||||
|
ViewGroup parent) {
|
||||||
|
PGPSecretKeyRing keyRing = Apg.getSecretKeyRings().get(groupPosition);
|
||||||
|
Vector<KeyChild> children = getChildrenOfKeyRing(keyRing);
|
||||||
|
|
||||||
|
KeyChild child = children.get(childPosition);
|
||||||
|
View view = null;
|
||||||
|
switch (child.type) {
|
||||||
|
case KeyChild.KEY: {
|
||||||
|
PGPSecretKey key = child.key;
|
||||||
|
if (key.isMasterKey()) {
|
||||||
|
view = mInflater.inflate(R.layout.key_list_child_item_master_key, null);
|
||||||
|
} else {
|
||||||
|
view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView keyId = (TextView) view.findViewById(R.id.key_id);
|
||||||
|
String keyIdStr = Long.toHexString(key.getKeyID() & 0xffffffffL);
|
||||||
|
while (keyIdStr.length() < 8) {
|
||||||
|
keyIdStr = "0" + keyIdStr;
|
||||||
|
}
|
||||||
|
keyId.setText(keyIdStr);
|
||||||
|
TextView keyDetails = (TextView) view.findViewById(R.id.key_details);
|
||||||
|
String algorithmStr = Apg.getAlgorithmInfo(key);
|
||||||
|
keyDetails.setText("(" + algorithmStr + ")");
|
||||||
|
|
||||||
|
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encrypt_key);
|
||||||
|
if (!Apg.isEncryptionKey(key)) {
|
||||||
|
encryptIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_sign_key);
|
||||||
|
if (!Apg.isSigningKey(key)) {
|
||||||
|
signIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KeyChild.USER_ID: {
|
||||||
|
view = mInflater.inflate(R.layout.key_list_child_item_user_id, null);
|
||||||
|
TextView userId = (TextView) view.findViewById(R.id.user_id);
|
||||||
|
userId.setText(child.userId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
259
src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.bouncycastle2.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle2.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.thialfihar.android.apg.utils.IterableIterator;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class SelectPublicKeyListActivity extends Activity {
|
||||||
|
protected Vector<PGPPublicKeyRing> mKeyRings;
|
||||||
|
protected LayoutInflater mInflater;
|
||||||
|
protected Intent mIntent;
|
||||||
|
protected ListView mList;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
|
||||||
|
// fill things
|
||||||
|
mIntent = getIntent();
|
||||||
|
long selectedKeyIds[] = null;
|
||||||
|
if (mIntent.getExtras() != null) {
|
||||||
|
selectedKeyIds = mIntent.getExtras().getLongArray("selection");
|
||||||
|
}
|
||||||
|
|
||||||
|
Apg.initialize(this);
|
||||||
|
mKeyRings = (Vector<PGPPublicKeyRing>) Apg.getPublicKeyRings().clone();
|
||||||
|
Collections.sort(mKeyRings, new Apg.PublicKeySorter());
|
||||||
|
|
||||||
|
setContentView(R.layout.select_public_key);
|
||||||
|
|
||||||
|
mList = (ListView) findViewById(R.id.list);
|
||||||
|
mList.setAdapter(new PublicKeyListAdapter(this));
|
||||||
|
if (selectedKeyIds != null) {
|
||||||
|
for (int i = 0; i < mKeyRings.size(); ++i) {
|
||||||
|
PGPPublicKeyRing keyRing = mKeyRings.get(i);
|
||||||
|
PGPPublicKey key = Apg.getMasterKey(keyRing);
|
||||||
|
if (key == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int j = 0; j < selectedKeyIds.length; ++j) {
|
||||||
|
if (key.getKeyID() == selectedKeyIds[j]) {
|
||||||
|
mList.setItemChecked(i, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button okButton = (Button) findViewById(R.id.btn_ok);
|
||||||
|
|
||||||
|
okButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
okClicked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Button cancelButton = (Button) findViewById(R.id.btn_cancel);
|
||||||
|
|
||||||
|
cancelButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
cancelClicked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelClicked() {
|
||||||
|
setResult(RESULT_CANCELED, null);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void okClicked() {
|
||||||
|
Intent data = new Intent();
|
||||||
|
Vector<Long> vector = new Vector<Long>();
|
||||||
|
for (int i = 0; i < mList.getCount(); ++i) {
|
||||||
|
if (mList.isItemChecked(i)) {
|
||||||
|
vector.add(mList.getItemIdAtPosition(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long selectedKeyIds[] = new long[vector.size()];
|
||||||
|
for (int i = 0; i < vector.size(); ++i) {
|
||||||
|
selectedKeyIds[i] = vector.get(i);
|
||||||
|
}
|
||||||
|
data.putExtra("selection", selectedKeyIds);
|
||||||
|
setResult(RESULT_OK, data);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PublicKeyListAdapter extends BaseAdapter {
|
||||||
|
public PublicKeyListAdapter(Context context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(int position) {
|
||||||
|
PGPPublicKeyRing keyRing = mKeyRings.get(position);
|
||||||
|
|
||||||
|
if (Apg.getMasterKey(keyRing) == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<PGPPublicKey> encryptKeys = Apg.getUsableEncryptKeys(keyRing);
|
||||||
|
if (encryptKeys.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasStableIds() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mKeyRings.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return mKeyRings.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
PGPPublicKeyRing keyRing = mKeyRings.get(position);
|
||||||
|
PGPPublicKey key = Apg.getMasterKey(keyRing);
|
||||||
|
if (key != null) {
|
||||||
|
return key.getKeyID();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View view = mInflater.inflate(R.layout.select_public_key_item, null);
|
||||||
|
boolean enabled = isEnabled(position);
|
||||||
|
|
||||||
|
PGPPublicKeyRing keyRing = mKeyRings.get(position);
|
||||||
|
PGPPublicKey key = null;
|
||||||
|
for (PGPPublicKey tKey : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
||||||
|
if (tKey.isMasterKey()) {
|
||||||
|
key = tKey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<PGPPublicKey> encryptKeys = Apg.getEncryptKeys(keyRing);
|
||||||
|
Vector<PGPPublicKey> usableKeys = Apg.getUsableEncryptKeys(keyRing);
|
||||||
|
|
||||||
|
TextView mainUserId = (TextView) view.findViewById(R.id.main_user_id);
|
||||||
|
mainUserId.setText(R.string.unknown_user_id);
|
||||||
|
TextView mainUserIdRest = (TextView) view.findViewById(R.id.main_user_id_rest);
|
||||||
|
mainUserIdRest.setText("");
|
||||||
|
TextView keyId = (TextView) view.findViewById(R.id.key_id);
|
||||||
|
keyId.setText("<no key>");
|
||||||
|
TextView creation = (TextView) view.findViewById(R.id.creation);
|
||||||
|
creation.setText("-");
|
||||||
|
TextView expiry = (TextView) view.findViewById(R.id.expiry);
|
||||||
|
expiry.setText("no expire");
|
||||||
|
TextView status = (TextView) view.findViewById(R.id.status);
|
||||||
|
status.setText("???");
|
||||||
|
|
||||||
|
if (key != null) {
|
||||||
|
String userId = Apg.getMainUserId(key);
|
||||||
|
if (userId != null) {
|
||||||
|
String chunks[] = userId.split(" <", 2);
|
||||||
|
userId = chunks[0];
|
||||||
|
if (chunks.length > 1) {
|
||||||
|
mainUserIdRest.setText("<" + chunks[1]);
|
||||||
|
}
|
||||||
|
mainUserId.setText(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyId.setText("" + Long.toHexString(key.getKeyID() & 0xffffffffL));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainUserIdRest.getText().length() == 0) {
|
||||||
|
mainUserIdRest.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPPublicKey timespanKey = key;
|
||||||
|
if (usableKeys.size() > 0) {
|
||||||
|
timespanKey = usableKeys.get(0);
|
||||||
|
status.setText("can encrypt");
|
||||||
|
} else if (encryptKeys.size() > 0) {
|
||||||
|
timespanKey = encryptKeys.get(0);
|
||||||
|
Date now = new Date();
|
||||||
|
if (now.compareTo(Apg.getCreationDate(timespanKey)) > 0) {
|
||||||
|
status.setText("not valid");
|
||||||
|
} else {
|
||||||
|
status.setText("expired");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status.setText("no key");
|
||||||
|
}
|
||||||
|
|
||||||
|
creation.setText(DateFormat.getDateInstance().format(Apg.getCreationDate(timespanKey)));
|
||||||
|
Date expiryDate = Apg.getExpiryDate(timespanKey);
|
||||||
|
if (expiryDate != null) {
|
||||||
|
expiry.setText(DateFormat.getDateInstance().format(expiryDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
status.setText(status.getText() + " ");
|
||||||
|
|
||||||
|
CheckBox selected = (CheckBox) view.findViewById(R.id.selected);
|
||||||
|
selected.setChecked(mList.isItemChecked(position));
|
||||||
|
|
||||||
|
view.setEnabled(enabled);
|
||||||
|
mainUserId.setEnabled(enabled);
|
||||||
|
mainUserIdRest.setEnabled(enabled);
|
||||||
|
keyId.setEnabled(enabled);
|
||||||
|
creation.setEnabled(enabled);
|
||||||
|
expiry.setEnabled(enabled);
|
||||||
|
selected.setEnabled(enabled);
|
||||||
|
status.setEnabled(enabled);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
209
src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.thialfihar.android.apg.utils.IterableIterator;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
|
||||||
|
public class SelectSecretKeyListActivity extends Activity {
|
||||||
|
protected Vector<PGPSecretKeyRing> mKeyRings;
|
||||||
|
protected LayoutInflater mInflater;
|
||||||
|
protected Intent mIntent;
|
||||||
|
protected ListView mList;
|
||||||
|
|
||||||
|
protected long mSelectedKeyId = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
|
||||||
|
// fill things
|
||||||
|
mIntent = getIntent();
|
||||||
|
|
||||||
|
Apg.initialize(this);
|
||||||
|
|
||||||
|
mKeyRings = (Vector<PGPSecretKeyRing>) Apg.getSecretKeyRings().clone();
|
||||||
|
Collections.sort(mKeyRings, new Apg.SecretKeySorter());
|
||||||
|
|
||||||
|
setContentView(R.layout.select_secret_key);
|
||||||
|
|
||||||
|
mList = (ListView) findViewById(R.id.list);
|
||||||
|
mList.setAdapter(new SecretKeyListAdapter(this));
|
||||||
|
|
||||||
|
mList.setOnItemClickListener(new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||||
|
Intent data = new Intent();
|
||||||
|
data.putExtra("selectedKeyId", id);
|
||||||
|
setResult(RESULT_OK, data);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SecretKeyListAdapter extends BaseAdapter {
|
||||||
|
|
||||||
|
public SecretKeyListAdapter(Context context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(int position) {
|
||||||
|
PGPSecretKeyRing keyRing = mKeyRings.get(position);
|
||||||
|
|
||||||
|
if (Apg.getMasterKey(keyRing) == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<PGPSecretKey> usableKeys = Apg.getUsableSigningKeys(keyRing);
|
||||||
|
if (usableKeys.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasStableIds() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mKeyRings.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return mKeyRings.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
PGPSecretKeyRing keyRing = mKeyRings.get(position);
|
||||||
|
PGPSecretKey key = Apg.getMasterKey(keyRing);
|
||||||
|
if (key != null) {
|
||||||
|
return key.getKeyID();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View view = mInflater.inflate(R.layout.select_secret_key_item, null);
|
||||||
|
boolean enabled = isEnabled(position);
|
||||||
|
|
||||||
|
PGPSecretKeyRing keyRing = mKeyRings.get(position);
|
||||||
|
PGPSecretKey key = null;
|
||||||
|
for (PGPSecretKey tKey : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||||
|
if (tKey.isMasterKey()) {
|
||||||
|
key = tKey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView mainUserId = (TextView) view.findViewById(R.id.main_user_id);
|
||||||
|
mainUserId.setText(R.string.unknown_user_id);
|
||||||
|
TextView mainUserIdRest = (TextView) view.findViewById(R.id.main_user_id_rest);
|
||||||
|
mainUserIdRest.setText("");
|
||||||
|
TextView keyId = (TextView) view.findViewById(R.id.key_id);
|
||||||
|
keyId.setText("<no key>");
|
||||||
|
TextView creation = (TextView) view.findViewById(R.id.creation);
|
||||||
|
creation.setText("");
|
||||||
|
TextView expiry = (TextView) view.findViewById(R.id.expiry);
|
||||||
|
expiry.setText("");
|
||||||
|
TextView status = (TextView) view.findViewById(R.id.status);
|
||||||
|
status.setText("???");
|
||||||
|
|
||||||
|
if (key != null) {
|
||||||
|
String userId = Apg.getMainUserId(key);
|
||||||
|
if (userId != null) {
|
||||||
|
String chunks[] = userId.split(" <", 2);
|
||||||
|
userId = chunks[0];
|
||||||
|
if (chunks.length > 1) {
|
||||||
|
mainUserIdRest.setText("<" + chunks[1]);
|
||||||
|
}
|
||||||
|
mainUserId.setText(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyId.setText("" + Long.toHexString(key.getKeyID() & 0xffffffffL));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainUserIdRest.getText().length() == 0) {
|
||||||
|
mainUserIdRest.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<PGPSecretKey> signingKeys = Apg.getSigningKeys(keyRing);
|
||||||
|
Vector<PGPSecretKey> usableKeys = Apg.getUsableSigningKeys(keyRing);
|
||||||
|
|
||||||
|
PGPSecretKey timespanKey = key;
|
||||||
|
if (usableKeys.size() > 0) {
|
||||||
|
timespanKey = usableKeys.get(0);
|
||||||
|
status.setText("can sign");
|
||||||
|
} else if (signingKeys.size() > 0) {
|
||||||
|
timespanKey = signingKeys.get(0);
|
||||||
|
Date now = new Date();
|
||||||
|
if (now.compareTo(Apg.getCreationDate(timespanKey)) > 0) {
|
||||||
|
status.setText("not valid");
|
||||||
|
} else {
|
||||||
|
status.setText("expired");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status.setText("no key");
|
||||||
|
}
|
||||||
|
|
||||||
|
creation.setText(DateFormat.getDateInstance().format(Apg.getCreationDate(timespanKey)));
|
||||||
|
Date expiryDate = Apg.getExpiryDate(timespanKey);
|
||||||
|
if (expiryDate != null) {
|
||||||
|
expiry.setText(DateFormat.getDateInstance().format(expiryDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
status.setText(status.getText() + " ");
|
||||||
|
|
||||||
|
view.setEnabled(enabled);
|
||||||
|
mainUserId.setEnabled(enabled);
|
||||||
|
mainUserIdRest.setEnabled(enabled);
|
||||||
|
keyId.setEnabled(enabled);
|
||||||
|
creation.setEnabled(enabled);
|
||||||
|
expiry.setEnabled(enabled);
|
||||||
|
status.setEnabled(enabled);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/org/thialfihar/android/apg/provider/Accounts.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.provider;
|
||||||
|
|
||||||
|
public class Accounts extends Accounts1 {
|
||||||
|
private Accounts() {
|
||||||
|
}
|
||||||
|
}
|
36
src/org/thialfihar/android/apg/provider/Accounts1.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.provider;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
|
class Accounts1 implements BaseColumns {
|
||||||
|
public static final String TABLE_NAME = "accounts";
|
||||||
|
|
||||||
|
public static final String _ID_type = "INTEGER PRIMARY KEY";
|
||||||
|
public static final String NAME = "c_name";
|
||||||
|
public static final String NAME_type = "TEXT";
|
||||||
|
|
||||||
|
public static final Uri CONTENT_URI =
|
||||||
|
Uri.parse("content://" + DataProvider.AUTHORITY + "/accounts");
|
||||||
|
public static final String CONTENT_TYPE =
|
||||||
|
"vnd.android.cursor.dir/vnd.thialfihar.apg.account";
|
||||||
|
public static final String CONTENT_ITEM_TYPE =
|
||||||
|
"vnd.android.cursor.item/vnd.thialfihar.apg.account";
|
||||||
|
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
|
||||||
|
}
|
494
src/org/thialfihar/android/apg/provider/DataProvider.java
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.provider;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.UriMatcher;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.SQLException;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
public class DataProvider extends ContentProvider {
|
||||||
|
public static final String AUTHORITY = "org.thialfihar.android.apg.provider";
|
||||||
|
|
||||||
|
private static final String DATABASE_NAME = "apg";
|
||||||
|
private static final int DATABASE_VERSION = 1;
|
||||||
|
|
||||||
|
private static final int PUBLIC_KEYS = 101;
|
||||||
|
private static final int PUBLIC_KEY_ID = 102;
|
||||||
|
private static final int PUBLIC_KEY_BY_KEY_ID = 103;
|
||||||
|
|
||||||
|
private static final int SECRET_KEYS = 201;
|
||||||
|
private static final int SECRET_KEY_ID = 202;
|
||||||
|
private static final int SECRET_KEY_BY_KEY_ID = 203;
|
||||||
|
|
||||||
|
private static final int ACCOUNTS = 301;
|
||||||
|
private static final int ACCOUNT_ID = 302;
|
||||||
|
|
||||||
|
private static final UriMatcher mUriMatcher;
|
||||||
|
private static final HashMap<String, String> mPublicKeysProjectionMap;
|
||||||
|
private static final HashMap<String, String> mSecretKeysProjectionMap;
|
||||||
|
private static final HashMap<String, String> mAccountsProjectionMap;
|
||||||
|
|
||||||
|
private DatabaseHelper mdbHelper;
|
||||||
|
|
||||||
|
static {
|
||||||
|
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
|
mUriMatcher.addURI(DataProvider.AUTHORITY, "public_keys", PUBLIC_KEYS);
|
||||||
|
mUriMatcher.addURI(DataProvider.AUTHORITY, "public_keys/#", PUBLIC_KEY_ID);
|
||||||
|
mUriMatcher.addURI(DataProvider.AUTHORITY, "public_keys/key_id/*", PUBLIC_KEY_BY_KEY_ID);
|
||||||
|
|
||||||
|
mUriMatcher.addURI(DataProvider.AUTHORITY, "secret_keys", SECRET_KEYS);
|
||||||
|
mUriMatcher.addURI(DataProvider.AUTHORITY, "secret_keys/#", SECRET_KEY_ID);
|
||||||
|
mUriMatcher.addURI(DataProvider.AUTHORITY, "secret_keys/key_id/*", SECRET_KEY_BY_KEY_ID);
|
||||||
|
|
||||||
|
mUriMatcher.addURI(DataProvider.AUTHORITY, "accounts", ACCOUNTS);
|
||||||
|
mUriMatcher.addURI(DataProvider.AUTHORITY, "accounts/#", ACCOUNT_ID);
|
||||||
|
|
||||||
|
mPublicKeysProjectionMap = new HashMap<String, String>();
|
||||||
|
mPublicKeysProjectionMap.put(PublicKeys._ID, PublicKeys._ID);
|
||||||
|
mPublicKeysProjectionMap.put(PublicKeys.KEY_ID, PublicKeys.KEY_ID);
|
||||||
|
mPublicKeysProjectionMap.put(PublicKeys.KEY_DATA, PublicKeys.KEY_DATA);
|
||||||
|
mPublicKeysProjectionMap.put(PublicKeys.WHO_ID, PublicKeys.WHO_ID);
|
||||||
|
|
||||||
|
mSecretKeysProjectionMap = new HashMap<String, String>();
|
||||||
|
mSecretKeysProjectionMap.put(PublicKeys._ID, PublicKeys._ID);
|
||||||
|
mSecretKeysProjectionMap.put(PublicKeys.KEY_ID, PublicKeys.KEY_ID);
|
||||||
|
mSecretKeysProjectionMap.put(PublicKeys.KEY_DATA, PublicKeys.KEY_DATA);
|
||||||
|
mSecretKeysProjectionMap.put(PublicKeys.WHO_ID, PublicKeys.WHO_ID);
|
||||||
|
|
||||||
|
mAccountsProjectionMap = new HashMap<String, String>();
|
||||||
|
mAccountsProjectionMap.put(Accounts._ID, Accounts._ID);
|
||||||
|
mAccountsProjectionMap.put(Accounts.NAME, Accounts.NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class helps open, create, and upgrade the database file.
|
||||||
|
*/
|
||||||
|
private static class DatabaseHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
|
DatabaseHelper(Context context) {
|
||||||
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL("CREATE TABLE " + PublicKeys.TABLE_NAME + " (" +
|
||||||
|
PublicKeys._ID + " " + PublicKeys._ID_type + "," +
|
||||||
|
PublicKeys.KEY_ID + " " + PublicKeys.KEY_ID_type + ", " +
|
||||||
|
PublicKeys.KEY_DATA + " " + PublicKeys.KEY_DATA_type + ", " +
|
||||||
|
PublicKeys.WHO_ID + " " + PublicKeys.WHO_ID_type + ");");
|
||||||
|
|
||||||
|
db.execSQL("CREATE TABLE " + SecretKeys.TABLE_NAME + " (" +
|
||||||
|
SecretKeys._ID + " " + SecretKeys._ID_type + "," +
|
||||||
|
SecretKeys.KEY_ID + " " + SecretKeys.KEY_ID_type + ", " +
|
||||||
|
SecretKeys.KEY_DATA + " " + SecretKeys.KEY_DATA_type + ", " +
|
||||||
|
SecretKeys.WHO_ID + " " + SecretKeys.WHO_ID_type + ");");
|
||||||
|
|
||||||
|
db.execSQL("CREATE TABLE " + Accounts.TABLE_NAME + " (" +
|
||||||
|
Accounts._ID + " " + Accounts._ID_type + "," +
|
||||||
|
Accounts.NAME + " " + Accounts.NAME_type + ");");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
int currentVersion = oldVersion;
|
||||||
|
while (currentVersion < newVersion) {
|
||||||
|
switch (currentVersion) {
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
mdbHelper = new DatabaseHelper(getContext());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor query(Uri uri, String[] projection, String selection,
|
||||||
|
String[] selectionArgs, String sortOrder) {
|
||||||
|
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||||
|
|
||||||
|
switch (mUriMatcher.match(uri)) {
|
||||||
|
case PUBLIC_KEYS: {
|
||||||
|
qb.setTables(PublicKeys.TABLE_NAME);
|
||||||
|
qb.setProjectionMap(mPublicKeysProjectionMap);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUBLIC_KEY_ID: {
|
||||||
|
qb.setTables(PublicKeys.TABLE_NAME);
|
||||||
|
qb.setProjectionMap(mPublicKeysProjectionMap);
|
||||||
|
qb.appendWhere(PublicKeys._ID + "=" + uri.getPathSegments().get(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUBLIC_KEY_BY_KEY_ID: {
|
||||||
|
qb.setTables(PublicKeys.TABLE_NAME);
|
||||||
|
qb.setProjectionMap(mPublicKeysProjectionMap);
|
||||||
|
qb.appendWhere(PublicKeys.KEY_ID + "=" + uri.getPathSegments().get(2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEYS: {
|
||||||
|
qb.setTables(SecretKeys.TABLE_NAME);
|
||||||
|
qb.setProjectionMap(mSecretKeysProjectionMap);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEY_ID: {
|
||||||
|
qb.setTables(SecretKeys.TABLE_NAME);
|
||||||
|
qb.setProjectionMap(mSecretKeysProjectionMap);
|
||||||
|
qb.appendWhere(SecretKeys._ID + "=" + uri.getPathSegments().get(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEY_BY_KEY_ID: {
|
||||||
|
qb.setTables(SecretKeys.TABLE_NAME);
|
||||||
|
qb.setProjectionMap(mSecretKeysProjectionMap);
|
||||||
|
qb.appendWhere(SecretKeys.KEY_ID + "=" + uri.getPathSegments().get(2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNTS: {
|
||||||
|
qb.setTables(Accounts.TABLE_NAME);
|
||||||
|
qb.setProjectionMap(mAccountsProjectionMap);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNT_ID: {
|
||||||
|
qb.setTables(Accounts.TABLE_NAME);
|
||||||
|
qb.setProjectionMap(mAccountsProjectionMap);
|
||||||
|
qb.appendWhere(Accounts._ID + "=" + uri.getPathSegments().get(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no sort order is specified use the default
|
||||||
|
String orderBy;
|
||||||
|
if (TextUtils.isEmpty(sortOrder)) {
|
||||||
|
orderBy = PublicKeys.DEFAULT_SORT_ORDER;
|
||||||
|
} else {
|
||||||
|
orderBy = sortOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the database and run the query
|
||||||
|
SQLiteDatabase db = mdbHelper.getReadableDatabase();
|
||||||
|
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
|
||||||
|
|
||||||
|
// Tell the cursor what uri to watch, so it knows when its source data
|
||||||
|
// changes
|
||||||
|
c.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType(Uri uri) {
|
||||||
|
switch (mUriMatcher.match(uri)) {
|
||||||
|
case PUBLIC_KEYS: {
|
||||||
|
return PublicKeys.CONTENT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUBLIC_KEY_ID: {
|
||||||
|
return PublicKeys.CONTENT_ITEM_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUBLIC_KEY_BY_KEY_ID: {
|
||||||
|
return PublicKeys.CONTENT_ITEM_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEYS: {
|
||||||
|
return SecretKeys.CONTENT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEY_ID: {
|
||||||
|
return SecretKeys.CONTENT_ITEM_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEY_BY_KEY_ID: {
|
||||||
|
return SecretKeys.CONTENT_ITEM_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNTS: {
|
||||||
|
return Accounts.CONTENT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNT_ID: {
|
||||||
|
return Accounts.CONTENT_ITEM_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(Uri uri, ContentValues initialValues) {
|
||||||
|
switch (mUriMatcher.match(uri)) {
|
||||||
|
case PUBLIC_KEYS: {
|
||||||
|
ContentValues values;
|
||||||
|
if (initialValues != null) {
|
||||||
|
values = new ContentValues(initialValues);
|
||||||
|
} else {
|
||||||
|
values = new ContentValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.containsKey(PublicKeys.WHO_ID)) {
|
||||||
|
values.put(PublicKeys.WHO_ID, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
SQLiteDatabase db = mdbHelper.getWritableDatabase();
|
||||||
|
long rowId = db.insert(PublicKeys.TABLE_NAME, PublicKeys.WHO_ID, values);
|
||||||
|
if (rowId > 0) {
|
||||||
|
Uri transferUri = ContentUris.withAppendedId(PublicKeys.CONTENT_URI, rowId);
|
||||||
|
getContext().getContentResolver().notifyChange(transferUri, null);
|
||||||
|
return transferUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SQLException("Failed to insert row into " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEYS: {
|
||||||
|
ContentValues values;
|
||||||
|
if (initialValues != null) {
|
||||||
|
values = new ContentValues(initialValues);
|
||||||
|
} else {
|
||||||
|
values = new ContentValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.containsKey(SecretKeys.WHO_ID)) {
|
||||||
|
values.put(SecretKeys.WHO_ID, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
SQLiteDatabase db = mdbHelper.getWritableDatabase();
|
||||||
|
long rowId = db.insert(SecretKeys.TABLE_NAME, SecretKeys.WHO_ID, values);
|
||||||
|
if (rowId > 0) {
|
||||||
|
Uri transferUri = ContentUris.withAppendedId(SecretKeys.CONTENT_URI, rowId);
|
||||||
|
getContext().getContentResolver().notifyChange(transferUri, null);
|
||||||
|
return transferUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SQLException("Failed to insert row into " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNTS: {
|
||||||
|
ContentValues values;
|
||||||
|
if (initialValues != null) {
|
||||||
|
values = new ContentValues(initialValues);
|
||||||
|
} else {
|
||||||
|
values = new ContentValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
SQLiteDatabase db = mdbHelper.getWritableDatabase();
|
||||||
|
long rowId = db.insert(Accounts.TABLE_NAME, null, values);
|
||||||
|
if (rowId > 0) {
|
||||||
|
Uri transferUri = ContentUris.withAppendedId(Accounts.CONTENT_URI, rowId);
|
||||||
|
getContext().getContentResolver().notifyChange(transferUri, null);
|
||||||
|
return transferUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SQLException("Failed to insert row into " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(Uri uri, String where, String[] whereArgs) {
|
||||||
|
SQLiteDatabase db = mdbHelper.getWritableDatabase();
|
||||||
|
int count;
|
||||||
|
switch (mUriMatcher.match(uri)) {
|
||||||
|
case PUBLIC_KEYS: {
|
||||||
|
count = db.delete(PublicKeys.TABLE_NAME, where, whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUBLIC_KEY_ID: {
|
||||||
|
String publicKeyId = uri.getPathSegments().get(1);
|
||||||
|
count = db.delete(PublicKeys.TABLE_NAME,
|
||||||
|
PublicKeys._ID + "=" + publicKeyId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUBLIC_KEY_BY_KEY_ID: {
|
||||||
|
String publicKeyKeyId = uri.getPathSegments().get(2);
|
||||||
|
count = db.delete(PublicKeys.TABLE_NAME,
|
||||||
|
PublicKeys.KEY_ID + "=" + publicKeyKeyId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEYS: {
|
||||||
|
count = db.delete(SecretKeys.TABLE_NAME, where, whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEY_ID: {
|
||||||
|
String secretKeyId = uri.getPathSegments().get(1);
|
||||||
|
count = db.delete(SecretKeys.TABLE_NAME,
|
||||||
|
SecretKeys._ID + "=" + secretKeyId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEY_BY_KEY_ID: {
|
||||||
|
String secretKeyKeyId = uri.getPathSegments().get(2);
|
||||||
|
count = db.delete(SecretKeys.TABLE_NAME,
|
||||||
|
SecretKeys.KEY_ID + "=" + secretKeyKeyId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNTS: {
|
||||||
|
count = db.delete(Accounts.TABLE_NAME, where, whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNT_ID: {
|
||||||
|
String accountId = uri.getPathSegments().get(1);
|
||||||
|
count = db.delete(Accounts.TABLE_NAME,
|
||||||
|
Accounts._ID + "=" + accountId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
|
||||||
|
SQLiteDatabase db = mdbHelper.getWritableDatabase();
|
||||||
|
int count;
|
||||||
|
switch (mUriMatcher.match(uri)) {
|
||||||
|
case PUBLIC_KEYS: {
|
||||||
|
count = db.update(PublicKeys.TABLE_NAME, values, where, whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUBLIC_KEY_ID: {
|
||||||
|
String publicKeyId = uri.getPathSegments().get(1);
|
||||||
|
|
||||||
|
count = db.update(PublicKeys.TABLE_NAME, values,
|
||||||
|
PublicKeys._ID + "=" + publicKeyId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PUBLIC_KEY_BY_KEY_ID: {
|
||||||
|
String publicKeyKeyId = uri.getPathSegments().get(2);
|
||||||
|
|
||||||
|
count = db.update(PublicKeys.TABLE_NAME, values,
|
||||||
|
PublicKeys.KEY_ID + "=" + publicKeyKeyId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEYS: {
|
||||||
|
count = db.update(SecretKeys.TABLE_NAME, values, where, whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEY_ID: {
|
||||||
|
String secretKeyId = uri.getPathSegments().get(1);
|
||||||
|
|
||||||
|
count = db.update(SecretKeys.TABLE_NAME, values,
|
||||||
|
SecretKeys._ID + "=" + secretKeyId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SECRET_KEY_BY_KEY_ID: {
|
||||||
|
String secretKeyKeyId = uri.getPathSegments().get(2);
|
||||||
|
|
||||||
|
count = db.update(SecretKeys.TABLE_NAME, values,
|
||||||
|
SecretKeys.KEY_ID + "=" + secretKeyKeyId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNTS: {
|
||||||
|
count = db.update(Accounts.TABLE_NAME, values, where, whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ACCOUNT_ID: {
|
||||||
|
String accountId = uri.getPathSegments().get(1);
|
||||||
|
|
||||||
|
count = db.update(Accounts.TABLE_NAME, values,
|
||||||
|
Accounts._ID + "=" + accountId +
|
||||||
|
(!TextUtils.isEmpty(where) ?
|
||||||
|
" AND (" + where + ')' : ""),
|
||||||
|
whereArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
22
src/org/thialfihar/android/apg/provider/PublicKeys.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.provider;
|
||||||
|
|
||||||
|
public class PublicKeys extends PublicKeys1 {
|
||||||
|
private PublicKeys() {
|
||||||
|
}
|
||||||
|
}
|
42
src/org/thialfihar/android/apg/provider/PublicKeys1.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.provider;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
|
class PublicKeys1 implements BaseColumns {
|
||||||
|
public static final String TABLE_NAME = "public_keys";
|
||||||
|
|
||||||
|
public static final String _ID_type = "INTEGER PRIMARY KEY";
|
||||||
|
public static final String KEY_ID = "c_key_id";
|
||||||
|
public static final String KEY_ID_type = "INT64";
|
||||||
|
public static final String KEY_DATA = "c_key_data";
|
||||||
|
public static final String KEY_DATA_type = "BLOB";
|
||||||
|
public static final String WHO_ID = "c_who_id";
|
||||||
|
public static final String WHO_ID_type = "INTEGER";
|
||||||
|
|
||||||
|
public static final Uri CONTENT_URI =
|
||||||
|
Uri.parse("content://" + DataProvider.AUTHORITY + "/public_keys");
|
||||||
|
public static final Uri CONTENT_URI_BY_KEY_ID =
|
||||||
|
Uri.parse("content://" + DataProvider.AUTHORITY + "/public_keys/key_id");
|
||||||
|
public static final String CONTENT_TYPE =
|
||||||
|
"vnd.android.cursor.dir/vnd.thialfihar.apg.public_key";
|
||||||
|
public static final String CONTENT_ITEM_TYPE =
|
||||||
|
"vnd.android.cursor.item/vnd.thialfihar.apg.public_key";
|
||||||
|
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
|
||||||
|
}
|
22
src/org/thialfihar/android/apg/provider/SecretKeys.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.provider;
|
||||||
|
|
||||||
|
public class SecretKeys extends SecretKeys1 {
|
||||||
|
private SecretKeys() {
|
||||||
|
}
|
||||||
|
}
|
42
src/org/thialfihar/android/apg/provider/SecretKeys1.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.provider;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
|
class SecretKeys1 implements BaseColumns {
|
||||||
|
public static final String TABLE_NAME = "secret_keys";
|
||||||
|
|
||||||
|
public static final String _ID_type = "INTEGER PRIMARY KEY";
|
||||||
|
public static final String KEY_ID = "c_key_id";
|
||||||
|
public static final String KEY_ID_type = "INT64";
|
||||||
|
public static final String KEY_DATA = "c_key_data";
|
||||||
|
public static final String KEY_DATA_type = "BLOB";
|
||||||
|
public static final String WHO_ID = "c_who_id";
|
||||||
|
public static final String WHO_ID_type = "INTEGER";
|
||||||
|
|
||||||
|
public static final Uri CONTENT_URI =
|
||||||
|
Uri.parse("content://" + DataProvider.AUTHORITY + "/secret_keys");
|
||||||
|
public static final Uri CONTENT_URI_BY_KEY_ID =
|
||||||
|
Uri.parse("content://" + DataProvider.AUTHORITY + "/secret_keys/key_id");
|
||||||
|
public static final String CONTENT_TYPE =
|
||||||
|
"vnd.android.cursor.dir/vnd.thialfihar.apg.secret_key";
|
||||||
|
public static final String CONTENT_ITEM_TYPE =
|
||||||
|
"vnd.android.cursor.item/vnd.thialfihar.apg.secret_key";
|
||||||
|
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
|
||||||
|
}
|
25
src/org/thialfihar/android/apg/ui/widget/Editor.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.ui.widget;
|
||||||
|
|
||||||
|
public interface Editor {
|
||||||
|
public interface EditorListener {
|
||||||
|
public void onDeleted(Editor editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEditorListener(EditorListener listener);
|
||||||
|
}
|
248
src/org/thialfihar/android/apg/ui/widget/KeyEditor.java
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.ui.widget;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||||
|
import org.thialfihar.android.apg.Apg;
|
||||||
|
import org.thialfihar.android.apg.R;
|
||||||
|
import org.thialfihar.android.apg.utils.Choice;
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.DatePicker;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
|
||||||
|
private PGPSecretKey mKey;
|
||||||
|
|
||||||
|
private EditorListener mEditorListener = null;
|
||||||
|
|
||||||
|
private boolean mIsMasterKey;
|
||||||
|
ImageButton mDeleteButton;
|
||||||
|
TextView mAlgorithm;
|
||||||
|
TextView mKeyId;
|
||||||
|
Spinner mUsage;
|
||||||
|
TextView mCreationDate;
|
||||||
|
Button mExpiryDateButton;
|
||||||
|
GregorianCalendar mExpiryDate;
|
||||||
|
|
||||||
|
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
|
||||||
|
new DatePickerDialog.OnDateSetListener() {
|
||||||
|
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
|
||||||
|
GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth);
|
||||||
|
setExpiryDate(date);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static class AlgorithmChoice extends Choice {
|
||||||
|
public static final int DSA = 1;
|
||||||
|
public static final int ELGAMAL = 2;
|
||||||
|
public static final int RSA = 3;
|
||||||
|
|
||||||
|
public AlgorithmChoice(int id, String name) {
|
||||||
|
super(id, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UsageChoice extends Choice {
|
||||||
|
public static final int SIGN_ONLY = 1;
|
||||||
|
public static final int ENCRYPT_ONLY = 2;
|
||||||
|
public static final int SIGN_AND_ENCRYPT = 3;
|
||||||
|
|
||||||
|
public UsageChoice(int id, String name) {
|
||||||
|
super(id, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyEditor(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyEditor(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
setDrawingCacheEnabled(true);
|
||||||
|
setAlwaysDrawnWithCacheEnabled(true);
|
||||||
|
|
||||||
|
mAlgorithm = (TextView) findViewById(R.id.algorithm);
|
||||||
|
mKeyId = (TextView) findViewById(R.id.key_id);
|
||||||
|
mCreationDate = (TextView) findViewById(R.id.creation);
|
||||||
|
mExpiryDateButton = (Button) findViewById(R.id.expiry);
|
||||||
|
mUsage = (Spinner) findViewById(R.id.usage);
|
||||||
|
KeyEditor.UsageChoice choices[] = {
|
||||||
|
new KeyEditor.UsageChoice(KeyEditor.UsageChoice.SIGN_ONLY,
|
||||||
|
getResources().getString(R.string.sign_only)),
|
||||||
|
new KeyEditor.UsageChoice(KeyEditor.UsageChoice.ENCRYPT_ONLY,
|
||||||
|
getResources().getString(R.string.encrypt_only)),
|
||||||
|
new KeyEditor.UsageChoice(KeyEditor.UsageChoice.SIGN_AND_ENCRYPT,
|
||||||
|
getResources().getString(R.string.sign_and_encrypt)),
|
||||||
|
};
|
||||||
|
ArrayAdapter<KeyEditor.UsageChoice> adapter =
|
||||||
|
new ArrayAdapter<KeyEditor.UsageChoice>(getContext(),
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
choices);
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
mUsage.setAdapter(adapter);
|
||||||
|
|
||||||
|
mDeleteButton = (ImageButton) findViewById(R.id.edit_delete);
|
||||||
|
mDeleteButton.setOnClickListener(this);
|
||||||
|
|
||||||
|
setExpiryDate(null);
|
||||||
|
|
||||||
|
mExpiryDateButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
GregorianCalendar date = mExpiryDate;
|
||||||
|
if (date == null) {
|
||||||
|
date = new GregorianCalendar();
|
||||||
|
}
|
||||||
|
|
||||||
|
DatePickerDialog dialog =
|
||||||
|
new DatePickerDialog(getContext(), mExpiryDateSetListener,
|
||||||
|
date.get(Calendar.YEAR),
|
||||||
|
date.get(Calendar.MONTH),
|
||||||
|
date.get(Calendar.DAY_OF_MONTH));
|
||||||
|
dialog.setCancelable(true);
|
||||||
|
dialog.setButton(Dialog.BUTTON_NEGATIVE, "None",
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
setExpiryDate(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
super.onFinishInflate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(PGPSecretKey key, boolean isMasterKey) {
|
||||||
|
mKey = key;
|
||||||
|
|
||||||
|
mIsMasterKey = isMasterKey;
|
||||||
|
if (mIsMasterKey) {
|
||||||
|
mDeleteButton.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
mAlgorithm.setText(Apg.getAlgorithmInfo(key));
|
||||||
|
String keyId1Str = Long.toHexString((key.getKeyID() >> 32) & 0xffffffffL);
|
||||||
|
while (keyId1Str.length() < 8) {
|
||||||
|
keyId1Str = "0" + keyId1Str;
|
||||||
|
}
|
||||||
|
String keyId2Str = Long.toHexString(key.getKeyID() & 0xffffffffL);
|
||||||
|
while (keyId2Str.length() < 8) {
|
||||||
|
keyId2Str = "0" + keyId2Str;
|
||||||
|
}
|
||||||
|
mKeyId.setText(keyId1Str + " " + keyId2Str);
|
||||||
|
|
||||||
|
Vector<KeyEditor.UsageChoice> choices = new Vector<KeyEditor.UsageChoice>();
|
||||||
|
choices.add(new KeyEditor.UsageChoice(KeyEditor.UsageChoice.SIGN_ONLY,
|
||||||
|
getResources().getString(R.string.sign_only)));
|
||||||
|
if (!mIsMasterKey) {
|
||||||
|
choices.add(new KeyEditor.UsageChoice(KeyEditor.UsageChoice.ENCRYPT_ONLY,
|
||||||
|
getResources().getString(R.string.encrypt_only)));
|
||||||
|
}
|
||||||
|
choices.add(new KeyEditor.UsageChoice(KeyEditor.UsageChoice.SIGN_AND_ENCRYPT,
|
||||||
|
getResources().getString(R.string.sign_and_encrypt)));
|
||||||
|
|
||||||
|
ArrayAdapter<KeyEditor.UsageChoice> adapter =
|
||||||
|
new ArrayAdapter<KeyEditor.UsageChoice>(getContext(),
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
choices);
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
mUsage.setAdapter(adapter);
|
||||||
|
|
||||||
|
if (Apg.isEncryptionKey(key)) {
|
||||||
|
if (Apg.isSigningKey(key)) {
|
||||||
|
mUsage.setSelection(2);
|
||||||
|
} else {
|
||||||
|
mUsage.setSelection(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mUsage.setSelection(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
GregorianCalendar cal = new GregorianCalendar();
|
||||||
|
cal.setTime(Apg.getCreationDate(key));
|
||||||
|
mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime()));
|
||||||
|
cal = new GregorianCalendar();
|
||||||
|
Date date = Apg.getExpiryDate(key);
|
||||||
|
if (date == null) {
|
||||||
|
setExpiryDate(null);
|
||||||
|
} else {
|
||||||
|
cal.setTime(Apg.getExpiryDate(key));
|
||||||
|
setExpiryDate(cal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPSecretKey getValue() {
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
final ViewGroup parent = (ViewGroup)getParent();
|
||||||
|
if (v == mDeleteButton) {
|
||||||
|
parent.removeView(this);
|
||||||
|
if (mEditorListener != null) {
|
||||||
|
mEditorListener.onDeleted(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEditorListener(EditorListener listener) {
|
||||||
|
mEditorListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setExpiryDate(GregorianCalendar date) {
|
||||||
|
mExpiryDate = date;
|
||||||
|
if (date == null) {
|
||||||
|
mExpiryDateButton.setText(R.string.none);
|
||||||
|
} else {
|
||||||
|
mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GregorianCalendar getExpiryDate() {
|
||||||
|
return mExpiryDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsageChoice getUsage() {
|
||||||
|
return (UsageChoice) mUsage.getSelectedItem();
|
||||||
|
}
|
||||||
|
}
|
323
src/org/thialfihar/android/apg/ui/widget/SectionView.java
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.ui.widget;
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.bouncycastle2.openpgp.PGPException;
|
||||||
|
import org.bouncycastle2.openpgp.PGPSecretKey;
|
||||||
|
import org.thialfihar.android.apg.Apg;
|
||||||
|
import org.thialfihar.android.apg.R;
|
||||||
|
import org.thialfihar.android.apg.ui.widget.Editor.EditorListener;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable {
|
||||||
|
public static final int TYPE_USER_ID = 1;
|
||||||
|
public static final int TYPE_KEY = 2;
|
||||||
|
|
||||||
|
private LayoutInflater mInflater;
|
||||||
|
private View mAdd;
|
||||||
|
private ViewGroup mEditors;
|
||||||
|
private TextView mTitle;
|
||||||
|
private int mType = 0;
|
||||||
|
|
||||||
|
private KeyEditor.AlgorithmChoice mNewKeyAlgorithmChoice;
|
||||||
|
private int mNewKeySize;
|
||||||
|
|
||||||
|
volatile private PGPSecretKey mNewKey;
|
||||||
|
private ProgressDialog mProgressDialog;
|
||||||
|
private Thread mRunningThread = null;
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
Bundle data = msg.getData();
|
||||||
|
if (data != null) {
|
||||||
|
boolean closeProgressDialog = data.getBoolean("closeProgressDialog");
|
||||||
|
if (closeProgressDialog) {
|
||||||
|
if (mProgressDialog != null) {
|
||||||
|
mProgressDialog.dismiss();
|
||||||
|
mProgressDialog = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String error = data.getString("error");
|
||||||
|
if (error != null) {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
"Error: " + error,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean gotNewKey = data.getBoolean("gotNewKey");
|
||||||
|
if (gotNewKey) {
|
||||||
|
KeyEditor view =
|
||||||
|
(KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
|
||||||
|
mEditors, false);
|
||||||
|
view.setEditorListener(SectionView.this);
|
||||||
|
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||||
|
view.setValue(mNewKey, isMasterKey);
|
||||||
|
mEditors.addView(view);
|
||||||
|
SectionView.this.updateEditorsVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public SectionView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SectionView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewGroup getEditors() {
|
||||||
|
return mEditors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(int type) {
|
||||||
|
mType = type;
|
||||||
|
switch (type) {
|
||||||
|
case TYPE_USER_ID: {
|
||||||
|
mTitle.setText(R.string.section_userIds);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TYPE_KEY: {
|
||||||
|
mTitle.setText(R.string.section_keys);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
|
||||||
|
setDrawingCacheEnabled(true);
|
||||||
|
setAlwaysDrawnWithCacheEnabled(true);
|
||||||
|
|
||||||
|
mAdd = findViewById(R.id.header);
|
||||||
|
mAdd.setOnClickListener(this);
|
||||||
|
|
||||||
|
mEditors = (ViewGroup) findViewById(R.id.editors);
|
||||||
|
mTitle = (TextView) findViewById(R.id.title);
|
||||||
|
|
||||||
|
updateEditorsVisible();
|
||||||
|
super.onFinishInflate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void onDeleted(Editor editor) {
|
||||||
|
this.updateEditorsVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateEditorsVisible() {
|
||||||
|
final boolean hasChildren = mEditors.getChildCount() > 0;
|
||||||
|
mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void onClick(View v) {
|
||||||
|
switch (mType) {
|
||||||
|
case TYPE_USER_ID: {
|
||||||
|
UserIdEditor view =
|
||||||
|
(UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
|
||||||
|
mEditors, false);
|
||||||
|
view.setEditorListener(this);
|
||||||
|
if (mEditors.getChildCount() == 0) {
|
||||||
|
view.setIsMainUserId(true);
|
||||||
|
}
|
||||||
|
mEditors.addView(view);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TYPE_KEY: {
|
||||||
|
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
|
||||||
|
|
||||||
|
View view = mInflater.inflate(R.layout.create_key, null);
|
||||||
|
dialog.setView(view);
|
||||||
|
dialog.setTitle("Create Key");
|
||||||
|
|
||||||
|
final Spinner algorithm = (Spinner) view.findViewById(R.id.algorithm);
|
||||||
|
KeyEditor.AlgorithmChoice choices[] = {
|
||||||
|
new KeyEditor.AlgorithmChoice(KeyEditor.AlgorithmChoice.DSA,
|
||||||
|
getResources().getString(R.string.dsa)),
|
||||||
|
/*new KeyEditor.AlgorithmChoice(KeyEditor.AlgorithmChoice.ELGAMAL,
|
||||||
|
getResources().getString(R.string.elgamal)),*/
|
||||||
|
new KeyEditor.AlgorithmChoice(KeyEditor.AlgorithmChoice.RSA,
|
||||||
|
getResources().getString(R.string.rsa)),
|
||||||
|
};
|
||||||
|
ArrayAdapter<KeyEditor.AlgorithmChoice> adapter =
|
||||||
|
new ArrayAdapter<KeyEditor.AlgorithmChoice>(
|
||||||
|
getContext(),
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
choices);
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
algorithm.setAdapter(adapter);
|
||||||
|
// make RSA the default
|
||||||
|
for (int i = 0; i < choices.length; ++i) {
|
||||||
|
if (choices[i].getId() == KeyEditor.AlgorithmChoice.RSA) {
|
||||||
|
algorithm.setSelection(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final EditText keySize = (EditText) view.findViewById(R.id.size);
|
||||||
|
|
||||||
|
dialog.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface di, int id) {
|
||||||
|
di.dismiss();
|
||||||
|
try {
|
||||||
|
mNewKeySize = Integer.parseInt("" + keySize.getText());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
mNewKeySize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mNewKeyAlgorithmChoice =
|
||||||
|
(KeyEditor.AlgorithmChoice) algorithm.getSelectedItem();
|
||||||
|
createKey();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.setCancelable(true);
|
||||||
|
dialog.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface di, int id) {
|
||||||
|
di.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.create().show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateEditorsVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserIds(Vector<String> list) {
|
||||||
|
if (mType != TYPE_USER_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mEditors.removeAllViews();
|
||||||
|
for (String userId : list) {
|
||||||
|
UserIdEditor view =
|
||||||
|
(UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, mEditors, false);
|
||||||
|
view.setEditorListener(this);
|
||||||
|
view.setValue(userId);
|
||||||
|
if (mEditors.getChildCount() == 0) {
|
||||||
|
view.setIsMainUserId(true);
|
||||||
|
}
|
||||||
|
mEditors.addView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateEditorsVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeys(Vector<PGPSecretKey> list) {
|
||||||
|
if (mType != TYPE_KEY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mEditors.removeAllViews();
|
||||||
|
for (PGPSecretKey key : list) {
|
||||||
|
KeyEditor view =
|
||||||
|
(KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, false);
|
||||||
|
view.setEditorListener(this);
|
||||||
|
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||||
|
view.setValue(key, isMasterKey);
|
||||||
|
mEditors.addView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateEditorsVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createKey() {
|
||||||
|
mProgressDialog = new ProgressDialog(getContext());
|
||||||
|
mProgressDialog.setMessage("Generating key, this can take a while...");
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
|
mProgressDialog.show();
|
||||||
|
mRunningThread = new Thread(this);
|
||||||
|
mRunningThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
String error = null;
|
||||||
|
try {
|
||||||
|
mNewKey = Apg.createKey(mNewKeyAlgorithmChoice, mNewKeySize, Apg.getPassPhrase());
|
||||||
|
} catch (NoSuchProviderException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (PGPException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (InvalidParameterException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
} catch (Apg.GeneralException e) {
|
||||||
|
error = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Message message = new Message();
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putBoolean("closeProgressDialog", true);
|
||||||
|
if (error != null) {
|
||||||
|
data.putString("error", error);
|
||||||
|
} else {
|
||||||
|
data.putBoolean("gotNewKey", true);
|
||||||
|
}
|
||||||
|
message.setData(data);
|
||||||
|
mHandler.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
193
src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.ui.widget;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.thialfihar.android.apg.R;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
|
||||||
|
public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
|
||||||
|
private EditorListener mEditorListener = null;
|
||||||
|
|
||||||
|
private ImageButton mDeleteButton;
|
||||||
|
private RadioButton mIsMainUserId;
|
||||||
|
private EditText mName;
|
||||||
|
private EditText mEmail;
|
||||||
|
private EditText mComment;
|
||||||
|
|
||||||
|
private static final Pattern EMAIL_PATTERN =
|
||||||
|
Pattern.compile("^([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+[.]([a-zA-Z])+([a-zA-Z])+",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
public static class NoNameException extends Exception {
|
||||||
|
static final long serialVersionUID = 0xf812773343L;
|
||||||
|
|
||||||
|
public NoNameException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoEmailException extends Exception {
|
||||||
|
static final long serialVersionUID = 0xf812773344L;
|
||||||
|
|
||||||
|
public NoEmailException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InvalidEmailException extends Exception {
|
||||||
|
static final long serialVersionUID = 0xf812773345L;
|
||||||
|
|
||||||
|
public InvalidEmailException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserIdEditor(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserIdEditor(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
setDrawingCacheEnabled(true);
|
||||||
|
setAlwaysDrawnWithCacheEnabled(true);
|
||||||
|
|
||||||
|
mDeleteButton = (ImageButton) findViewById(R.id.edit_delete);
|
||||||
|
mDeleteButton.setOnClickListener(this);
|
||||||
|
mIsMainUserId = (RadioButton) findViewById(R.id.is_main_user_id);
|
||||||
|
mIsMainUserId.setOnClickListener(this);
|
||||||
|
|
||||||
|
mName = (EditText) findViewById(R.id.name);
|
||||||
|
mEmail = (EditText) findViewById(R.id.email);
|
||||||
|
mComment = (EditText) findViewById(R.id.comment);
|
||||||
|
|
||||||
|
super.onFinishInflate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String userId) {
|
||||||
|
mName.setText("");
|
||||||
|
mComment.setText("");
|
||||||
|
mEmail.setText("");
|
||||||
|
|
||||||
|
Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$");
|
||||||
|
Matcher matcher = withComment.matcher(userId);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
mName.setText(matcher.group(1));
|
||||||
|
mComment.setText(matcher.group(2));
|
||||||
|
mEmail.setText(matcher.group(3));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$");
|
||||||
|
matcher = withoutComment.matcher(userId);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
mName.setText(matcher.group(1));
|
||||||
|
mEmail.setText(matcher.group(2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() throws NoNameException, NoEmailException, InvalidEmailException {
|
||||||
|
String name = ("" + mName.getText()).trim();
|
||||||
|
String email = ("" + mEmail.getText()).trim();
|
||||||
|
String comment = ("" + mComment.getText()).trim();
|
||||||
|
|
||||||
|
if (email.length() > 0) {
|
||||||
|
Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
|
||||||
|
if (!emailMatcher.matches()) {
|
||||||
|
throw new InvalidEmailException("invalid email '" + email + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String userId = name;
|
||||||
|
if (comment.length() > 0) {
|
||||||
|
userId += " (" + comment + ")";
|
||||||
|
}
|
||||||
|
if (email.length() > 0) {
|
||||||
|
userId += " <" + email + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId.equals("")) {
|
||||||
|
// ok, empty one...
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise make sure that name and email exist
|
||||||
|
if (name.equals("")) {
|
||||||
|
throw new NoNameException("need a name");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email.equals("")) {
|
||||||
|
throw new NoEmailException("need an email");
|
||||||
|
}
|
||||||
|
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
final ViewGroup parent = (ViewGroup)getParent();
|
||||||
|
if (v == mDeleteButton) {
|
||||||
|
boolean wasMainUserId = mIsMainUserId.isChecked();
|
||||||
|
parent.removeView(this);
|
||||||
|
if (mEditorListener != null) {
|
||||||
|
mEditorListener.onDeleted(this);
|
||||||
|
}
|
||||||
|
if (wasMainUserId && parent.getChildCount() > 0) {
|
||||||
|
UserIdEditor editor = (UserIdEditor) parent.getChildAt(0);
|
||||||
|
editor.setIsMainUserId(true);
|
||||||
|
}
|
||||||
|
} else if (v == mIsMainUserId) {
|
||||||
|
for (int i = 0; i < parent.getChildCount(); ++i) {
|
||||||
|
UserIdEditor editor = (UserIdEditor) parent.getChildAt(i);
|
||||||
|
if (editor == this) {
|
||||||
|
editor.setIsMainUserId(true);
|
||||||
|
} else {
|
||||||
|
editor.setIsMainUserId(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsMainUserId(boolean value) {
|
||||||
|
mIsMainUserId.setChecked(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMainUserId() {
|
||||||
|
return mIsMainUserId.isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEditorListener(EditorListener listener) {
|
||||||
|
mEditorListener = listener;
|
||||||
|
}
|
||||||
|
}
|
44
src/org/thialfihar/android/apg/utils/Choice.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.utils;
|
||||||
|
|
||||||
|
public class Choice {
|
||||||
|
private String mName;
|
||||||
|
private int mId;
|
||||||
|
|
||||||
|
public Choice() {
|
||||||
|
mId = -1;
|
||||||
|
mName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Choice(int id, String name) {
|
||||||
|
mId = id;
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
}
|
31
src/org/thialfihar/android/apg/utils/IterableIterator.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thialfihar.android.apg.utils;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public class IterableIterator<T> implements Iterable<T> {
|
||||||
|
private Iterator<T> mIter;
|
||||||
|
|
||||||
|
public IterableIterator(Iterator<T> iter) {
|
||||||
|
mIter = iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return mIter;
|
||||||
|
}
|
||||||
|
}
|