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" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.fsck.k9.AccessibleEmailContentActivity"
|
||||
>
|
||||
</activity>
|
||||
|
||||
<receiver android:name="com.fsck.k9.service.BootReceiver"
|
||||
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:layout_height="wrap_content"
|
||||
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 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/attachments"
|
||||
|
@ -16,9 +16,12 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
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.LocalTextBody;
|
||||
import com.fsck.k9.provider.AttachmentProvider;
|
||||
import com.fsck.k9.web.AccessibleWebView;
|
||||
|
||||
public class MessageView extends K9Activity implements OnClickListener
|
||||
{
|
||||
@ -106,6 +110,11 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
private TextView mCryptoSignatureUserId = null;
|
||||
private TextView mCryptoSignatureUserIdRest = null;
|
||||
private WebView mMessageContentView;
|
||||
|
||||
private boolean mScreenReaderEnabled;
|
||||
|
||||
private AccessibleWebView mAccessibleMessageContentView;
|
||||
|
||||
private LinearLayout mHeaderContainer;
|
||||
private LinearLayout mAttachments;
|
||||
private LinearLayout mToContainerView;
|
||||
@ -285,9 +294,16 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
mHandler.post(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
if (mScreenReaderEnabled)
|
||||
{
|
||||
mAccessibleMessageContentView.zoomIn();
|
||||
}
|
||||
else
|
||||
{
|
||||
mMessageContentView.zoomIn();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
@ -295,9 +311,16 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
mHandler.post(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
if (mScreenReaderEnabled)
|
||||
{
|
||||
mAccessibleMessageContentView.zoomIn();
|
||||
}
|
||||
else
|
||||
{
|
||||
mMessageContentView.zoomOut();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
@ -749,6 +772,20 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
mTimeView = (TextView)findViewById(R.id.time);
|
||||
mTopView = mToggleScrollView = (ToggleScrollView)findViewById(R.id.top_view);
|
||||
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);
|
||||
mDecryptButton = (Button)findViewById(R.id.btn_decrypt);
|
||||
@ -1002,6 +1039,43 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
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
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
@ -2127,9 +2201,18 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
mMessageContentView.loadDataWithBaseURL("http://", emailText, mimeType, "utf-8", null);
|
||||
mTopView.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();
|
||||
}
|
||||
});
|
||||
|
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);
|
||||
}
|
||||
}
|