Merge branch 'ideal-screenreader'
* ideal-screenreader: The IDEAL Group have joined the K-9 dogwalkers and submitted their code to be part of K-9! Initial import of the Ideal K-9 branch which adds support for screenreaders.
@ -246,6 +246,10 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="com.fsck.k9.AccessibleEmailContentActivity"
|
||||||
|
>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<receiver android:name="com.fsck.k9.service.BootReceiver"
|
<receiver android:name="com.fsck.k9.service.BootReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
0
res/drawable/btn_check_buttonless_off.png
Executable file → Normal file
Before Width: | Height: | Size: 546 B After Width: | Height: | Size: 546 B |
0
res/drawable/btn_check_buttonless_on.png
Executable file → Normal file
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 384 B |
0
res/drawable/btn_check_small_off.png
Executable file → Normal file
Before Width: | Height: | Size: 362 B After Width: | Height: | Size: 362 B |
0
res/drawable/btn_check_small_on.png
Executable file → Normal file
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
0
res/drawable/btn_dialog_disable.png
Executable file → Normal file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
0
res/drawable/btn_dialog_normal.png
Executable file → Normal file
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
0
res/drawable/btn_dialog_pressed.png
Executable file → Normal file
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
0
res/drawable/btn_dialog_selected.png
Executable file → Normal file
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
12
res/layout/accessible_email_content.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
>
|
||||||
|
<ListView
|
||||||
|
android:id="@android:id/list"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
@ -251,6 +251,10 @@
|
|||||||
android:id="@+id/message_content"
|
android:id="@+id/message_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="fill_parent" />
|
android:layout_width="fill_parent" />
|
||||||
|
<com.fsck.k9.web.AccessibleWebView
|
||||||
|
android:id="@+id/accessible_message_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="fill_parent" />
|
||||||
<!-- Attachments area -->
|
<!-- Attachments area -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/attachments"
|
android:id="@+id/attachments"
|
||||||
|
@ -16,9 +16,12 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
@ -77,6 +80,7 @@ import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
|
|||||||
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalTextBody;
|
import com.fsck.k9.mail.store.LocalStore.LocalTextBody;
|
||||||
import com.fsck.k9.provider.AttachmentProvider;
|
import com.fsck.k9.provider.AttachmentProvider;
|
||||||
|
import com.fsck.k9.web.AccessibleWebView;
|
||||||
|
|
||||||
public class MessageView extends K9Activity implements OnClickListener
|
public class MessageView extends K9Activity implements OnClickListener
|
||||||
{
|
{
|
||||||
@ -106,6 +110,11 @@ public class MessageView extends K9Activity implements OnClickListener
|
|||||||
private TextView mCryptoSignatureUserId = null;
|
private TextView mCryptoSignatureUserId = null;
|
||||||
private TextView mCryptoSignatureUserIdRest = null;
|
private TextView mCryptoSignatureUserIdRest = null;
|
||||||
private WebView mMessageContentView;
|
private WebView mMessageContentView;
|
||||||
|
|
||||||
|
private boolean mScreenReaderEnabled;
|
||||||
|
|
||||||
|
private AccessibleWebView mAccessibleMessageContentView;
|
||||||
|
|
||||||
private LinearLayout mHeaderContainer;
|
private LinearLayout mHeaderContainer;
|
||||||
private LinearLayout mAttachments;
|
private LinearLayout mAttachments;
|
||||||
private LinearLayout mToContainerView;
|
private LinearLayout mToContainerView;
|
||||||
@ -286,7 +295,14 @@ public class MessageView extends K9Activity implements OnClickListener
|
|||||||
{
|
{
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
mMessageContentView.zoomIn();
|
if (mScreenReaderEnabled)
|
||||||
|
{
|
||||||
|
mAccessibleMessageContentView.zoomIn();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mMessageContentView.zoomIn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -296,7 +312,14 @@ public class MessageView extends K9Activity implements OnClickListener
|
|||||||
{
|
{
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
mMessageContentView.zoomOut();
|
if (mScreenReaderEnabled)
|
||||||
|
{
|
||||||
|
mAccessibleMessageContentView.zoomIn();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mMessageContentView.zoomOut();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -749,6 +772,20 @@ public class MessageView extends K9Activity implements OnClickListener
|
|||||||
mTimeView = (TextView)findViewById(R.id.time);
|
mTimeView = (TextView)findViewById(R.id.time);
|
||||||
mTopView = mToggleScrollView = (ToggleScrollView)findViewById(R.id.top_view);
|
mTopView = mToggleScrollView = (ToggleScrollView)findViewById(R.id.top_view);
|
||||||
mMessageContentView = (WebView)findViewById(R.id.message_content);
|
mMessageContentView = (WebView)findViewById(R.id.message_content);
|
||||||
|
mAccessibleMessageContentView = (AccessibleWebView) findViewById(R.id.accessible_message_content);
|
||||||
|
|
||||||
|
mScreenReaderEnabled = isScreenReaderActive();
|
||||||
|
|
||||||
|
if (mScreenReaderEnabled)
|
||||||
|
{
|
||||||
|
mAccessibleMessageContentView.setVisibility(View.VISIBLE);
|
||||||
|
mMessageContentView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mAccessibleMessageContentView.setVisibility(View.GONE);
|
||||||
|
mMessageContentView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
mDecryptLayout = (View)findViewById(R.id.layout_decrypt);
|
mDecryptLayout = (View)findViewById(R.id.layout_decrypt);
|
||||||
mDecryptButton = (Button)findViewById(R.id.btn_decrypt);
|
mDecryptButton = (Button)findViewById(R.id.btn_decrypt);
|
||||||
@ -1002,6 +1039,43 @@ public class MessageView extends K9Activity implements OnClickListener
|
|||||||
displayMessage(mMessageReference);
|
displayMessage(mMessageReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isScreenReaderActive()
|
||||||
|
{
|
||||||
|
final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService";
|
||||||
|
final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN";
|
||||||
|
// Restrict the set of intents to only accessibility services that have
|
||||||
|
// the category FEEDBACK_SPOKEN (aka, screen readers).
|
||||||
|
Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION);
|
||||||
|
screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY);
|
||||||
|
List<ResolveInfo> screenReaders = getPackageManager().queryIntentServices(
|
||||||
|
screenReaderIntent, 0);
|
||||||
|
ContentResolver cr = getContentResolver();
|
||||||
|
Cursor cursor = null;
|
||||||
|
int status = 0;
|
||||||
|
for (ResolveInfo screenReader : screenReaders)
|
||||||
|
{
|
||||||
|
// All screen readers are expected to implement a content provider
|
||||||
|
// that responds to
|
||||||
|
// content://<nameofpackage>.providers.StatusProvider
|
||||||
|
cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName
|
||||||
|
+ ".providers.StatusProvider"), null, null, null, null);
|
||||||
|
if (cursor != null)
|
||||||
|
{
|
||||||
|
cursor.moveToFirst();
|
||||||
|
// These content providers use a special cursor that only has
|
||||||
|
// one element,
|
||||||
|
// an integer that is 1 if the screen reader is running.
|
||||||
|
status = cursor.getInt(0);
|
||||||
|
cursor.close();
|
||||||
|
if (status == 1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState)
|
protected void onSaveInstanceState(Bundle outState)
|
||||||
{
|
{
|
||||||
@ -2127,9 +2201,18 @@ public class MessageView extends K9Activity implements OnClickListener
|
|||||||
{
|
{
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
mMessageContentView.loadDataWithBaseURL("http://", emailText, mimeType, "utf-8", null);
|
|
||||||
mTopView.scrollTo(0, 0);
|
mTopView.scrollTo(0, 0);
|
||||||
mMessageContentView.scrollTo(0, 0);
|
if (mScreenReaderEnabled)
|
||||||
|
{
|
||||||
|
mAccessibleMessageContentView.loadDataWithBaseURL("http://",
|
||||||
|
emailText, "text/html", "utf-8", null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mMessageContentView.loadDataWithBaseURL("http://", emailText,
|
||||||
|
"text/html", "utf-8", null);
|
||||||
|
mMessageContentView.scrollTo(0, 0);
|
||||||
|
}
|
||||||
updateDecryptLayout();
|
updateDecryptLayout();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
78
src/com/fsck/k9/web/AccessibleEmailContentActivity.java
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The IDEAL Group
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.fsck.k9.web;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import android.app.ListActivity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
|
public class AccessibleEmailContentActivity extends ListActivity {
|
||||||
|
String[] listItems = {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
private String htmlSource;
|
||||||
|
|
||||||
|
private ArrayList<String> cleanedList;
|
||||||
|
|
||||||
|
/** Called when the activity is first created. */
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
htmlSource = getIntent().getStringExtra("content");
|
||||||
|
Spanned parsedHtml = Html.fromHtml(htmlSource, null, null);
|
||||||
|
String[] rawListItems = parsedHtml.toString().split("\n");
|
||||||
|
|
||||||
|
cleanedList = new ArrayList<String>();
|
||||||
|
for (int i = 0; i < rawListItems.length; i++) {
|
||||||
|
if (rawListItems[i].trim().length() > 0) {
|
||||||
|
addToCleanedList(rawListItems[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listItems = cleanedList.toArray(listItems);
|
||||||
|
|
||||||
|
setContentView(com.fsck.k9.R.layout.accessible_email_content);
|
||||||
|
setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, listItems));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToCleanedList(String line) {
|
||||||
|
if (line.length() < 80) {
|
||||||
|
cleanedList.add(line);
|
||||||
|
} else {
|
||||||
|
while (line.length() > 80) {
|
||||||
|
int cutPoint = line.indexOf(" ", 80);
|
||||||
|
if ((cutPoint > 0) && (cutPoint < line.length())) {
|
||||||
|
cleanedList.add(line.substring(0, cutPoint));
|
||||||
|
line = line.substring(cutPoint).trim();
|
||||||
|
} else {
|
||||||
|
cleanedList.add(line);
|
||||||
|
line = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line.length() > 0) {
|
||||||
|
cleanedList.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
112
src/com/fsck/k9/web/AccessibleWebView.java
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The IDEAL Group
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.fsck.k9.web;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class AccessibleWebView extends TextView {
|
||||||
|
private Activity parent;
|
||||||
|
|
||||||
|
private String htmlSource;
|
||||||
|
|
||||||
|
private WebView dummyWebView;
|
||||||
|
|
||||||
|
public AccessibleWebView(Context context) {
|
||||||
|
super(context);
|
||||||
|
parent = (Activity) context;
|
||||||
|
dummyWebView = new WebView(context);
|
||||||
|
setFocusable(true);
|
||||||
|
setFocusableInTouchMode(true);
|
||||||
|
setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View arg0) {
|
||||||
|
diveIn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessibleWebView(Context context, AttributeSet attributes) {
|
||||||
|
super(context, attributes);
|
||||||
|
parent = (Activity) context;
|
||||||
|
dummyWebView = new WebView(context);
|
||||||
|
setFocusable(true);
|
||||||
|
setFocusableInTouchMode(true);
|
||||||
|
setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View arg0) {
|
||||||
|
diveIn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadData(String data, String mimeType, String encoding) {
|
||||||
|
htmlSource = data;
|
||||||
|
this.setText(Html.fromHtml(htmlSource, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSettings getSettings() {
|
||||||
|
return dummyWebView.getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVerticalScrollbarOverlay(boolean booleanValue) {
|
||||||
|
// Do nothing here; dummy stub method to maintain compatibility with
|
||||||
|
// standard WebView.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding,
|
||||||
|
String historyUrl) {
|
||||||
|
htmlSource = data;
|
||||||
|
this.setText(Html.fromHtml(htmlSource, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadUrl(String url) {
|
||||||
|
// Do nothing here; dummy stub method to maintain compatibility with
|
||||||
|
// standard WebView.
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean zoomIn() {
|
||||||
|
if (getTextSize() < 100) {
|
||||||
|
setTextSize(getTextSize() + 5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean zoomOut() {
|
||||||
|
if (getTextSize() > 5) {
|
||||||
|
setTextSize(getTextSize() - 5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void diveIn() {
|
||||||
|
Intent i = new Intent();
|
||||||
|
i.setClass(parent, AccessibleEmailContentActivity.class);
|
||||||
|
i.putExtra("content", htmlSource);
|
||||||
|
parent.startActivity(i);
|
||||||
|
}
|
||||||
|
}
|