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;
|
||||
}
|
||||
}
|