Wireframe email widget
* Formatting and assets are preliminary * Functionality correct * Needs cleanup, etc. Change-Id: I75051df93d233ef529a616c7a9efae403d320bd2
|
@ -14,47 +14,70 @@
|
|||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.email">
|
||||
|
||||
<original-package android:name="com.android.email" />
|
||||
<original-package
|
||||
android:name="com.android.email" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.READ_OWNER_DATA"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_CONTACTS"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_OWNER_DATA"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.INTERNET"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
|
||||
<!-- For EAS purposes; could be removed when EAS has a permanent home -->
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_CONTACTS"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_CALENDAR"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_CALENDAR"/>
|
||||
|
||||
<!-- Only required if a store implements push mail and needs to keep network open -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_PHONE_STATE"/>
|
||||
|
||||
<!-- Grant permission to other apps to view attachments -->
|
||||
<permission android:name="com.android.email.permission.READ_ATTACHMENT"
|
||||
android:permissionGroup="android.permission-group.MESSAGES"
|
||||
android:protectionLevel="dangerous"
|
||||
android:label="@string/read_attachment_label"
|
||||
android:description="@string/read_attachment_desc"/>
|
||||
<uses-permission android:name="com.android.email.permission.READ_ATTACHMENT"/>
|
||||
<permission
|
||||
android:name="com.android.email.permission.READ_ATTACHMENT"
|
||||
android:permissionGroup="android.permission-group.MESSAGES"
|
||||
android:protectionLevel="dangerous"
|
||||
android:label="@string/read_attachment_label"
|
||||
android:description="@string/read_attachment_desc"/>
|
||||
<uses-permission
|
||||
android:name="com.android.email.permission.READ_ATTACHMENT"/>
|
||||
|
||||
<!-- Grant permission to system apps to access provider (see provider below) -->
|
||||
<permission android:name="com.android.email.permission.ACCESS_PROVIDER"
|
||||
android:protectionLevel="signatureOrSystem"
|
||||
android:label="@string/permission_access_provider_label"
|
||||
android:description="@string/permission_access_provider_desc"/>
|
||||
<uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
|
||||
<permission
|
||||
android:name="com.android.email.permission.ACCESS_PROVIDER"
|
||||
android:protectionLevel="signatureOrSystem"
|
||||
android:label="@string/permission_access_provider_label"
|
||||
android:description="@string/permission_access_provider_desc"/>
|
||||
<uses-permission
|
||||
android:name="com.android.email.permission.ACCESS_PROVIDER"/>
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/icon"
|
||||
|
@ -68,9 +91,12 @@
|
|||
android:name=".activity.Welcome"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<action
|
||||
android:name="android.intent.action.MAIN" />
|
||||
<category
|
||||
android:name="android.intent.category.DEFAULT" />
|
||||
<category
|
||||
android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
|
@ -124,8 +150,10 @@
|
|||
android:label="@string/account_settings_action"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="com.android.email.activity.setup.ACCOUNT_MANAGER_ENTRY" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action
|
||||
android:name="com.android.email.activity.setup.ACCOUNT_MANAGER_ENTRY" />
|
||||
<category
|
||||
android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
|
@ -152,10 +180,11 @@
|
|||
android:theme="@android:style/Theme.Holo.DialogWhenLarge"
|
||||
>
|
||||
<intent-filter
|
||||
android:label="@string/account_shortcut_picker_name"
|
||||
>
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
android:label="@string/account_shortcut_picker_name">
|
||||
<action
|
||||
android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||
<category
|
||||
android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
@ -186,7 +215,8 @@
|
|||
>
|
||||
<intent-filter>
|
||||
<!-- This action is only to allow an entry point for launcher shortcuts -->
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action
|
||||
android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
@ -197,11 +227,16 @@
|
|||
<activity
|
||||
android:name=".activity.MessageFileView"
|
||||
>
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:mimeType="application/eml" />
|
||||
<data android:mimeType="message/rfc822" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<intent-filter
|
||||
android:label="@string/app_name">
|
||||
<action
|
||||
android:name="android.intent.action.VIEW" />
|
||||
<data
|
||||
android:mimeType="application/eml" />
|
||||
<data
|
||||
android:mimeType="message/rfc822" />
|
||||
<category
|
||||
android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
|
@ -210,45 +245,74 @@
|
|||
android:enabled="false"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.SENDTO" />
|
||||
<data android:scheme="mailto" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<action
|
||||
android:name="android.intent.action.VIEW" />
|
||||
<action
|
||||
android:name="android.intent.action.SENDTO" />
|
||||
<data
|
||||
android:scheme="mailto" />
|
||||
<category
|
||||
android:name="android.intent.category.DEFAULT" />
|
||||
<category
|
||||
android:name="android.intent.category.BROWSABLE" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<data android:mimeType="*/*" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<intent-filter
|
||||
android:label="@string/app_name">
|
||||
<action
|
||||
android:name="android.intent.action.SEND" />
|
||||
<data
|
||||
android:mimeType="*/*" />
|
||||
<category
|
||||
android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<data android:mimeType="*/*" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<intent-filter
|
||||
android:label="@string/app_name">
|
||||
<action
|
||||
android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<data
|
||||
android:mimeType="*/*" />
|
||||
<category
|
||||
android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="com.android.email.intent.action.REPLY" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!--EXCHANGE-REMOVE-SECTION-START-->
|
||||
<receiver android:name="com.android.exchange.EmailSyncAlarmReceiver"/>
|
||||
<receiver android:name="com.android.exchange.MailboxAlarmReceiver"/>
|
||||
<receiver
|
||||
android:name="com.android.exchange.EmailSyncAlarmReceiver"/>
|
||||
<receiver
|
||||
android:name="com.android.exchange.MailboxAlarmReceiver"/>
|
||||
<!--EXCHANGE-REMOVE-SECTION-END-->
|
||||
|
||||
<receiver android:name=".service.AttachmentDownloadService$Watchdog"
|
||||
<receiver
|
||||
android:name=".service.AttachmentDownloadService$Watchdog"
|
||||
android:enabled="true"/>
|
||||
|
||||
<receiver android:name=".service.EmailBroadcastReceiver" android:enabled="true">
|
||||
<receiver
|
||||
android:name=".service.EmailBroadcastReceiver"
|
||||
android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.DEVICE_STORAGE_LOW" />
|
||||
<action android:name="android.intent.action.DEVICE_STORAGE_OK" />
|
||||
<action
|
||||
android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action
|
||||
android:name="android.intent.action.DEVICE_STORAGE_LOW" />
|
||||
<action
|
||||
android:name="android.intent.action.DEVICE_STORAGE_OK" />
|
||||
</intent-filter>
|
||||
<!-- To handle secret code to activate the debug screen. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.provider.Telephony.SECRET_CODE" />
|
||||
<action
|
||||
android:name="android.provider.Telephony.SECRET_CODE" />
|
||||
<!-- "36245" = "email" -->
|
||||
<data android:scheme="android_secret_code" android:host="36245" />
|
||||
<data
|
||||
android:scheme="android_secret_code"
|
||||
android:host="36245" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service android:name=".service.EmailBroadcastProcessorService" />
|
||||
<service
|
||||
android:name=".service.EmailBroadcastProcessorService" />
|
||||
|
||||
<!-- Support for DeviceAdmin / DevicePolicyManager. See SecurityPolicy class for impl. -->
|
||||
<receiver
|
||||
|
@ -260,7 +324,8 @@
|
|||
android:name="android.app.device_admin"
|
||||
android:resource="@xml/device_admin" />
|
||||
<intent-filter>
|
||||
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
|
||||
<action
|
||||
android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
@ -290,7 +355,8 @@
|
|||
android:enabled="true"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
<action
|
||||
android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
|
@ -303,7 +369,8 @@
|
|||
android:name="com.android.email.service.PopImapSyncAdapterService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
<action
|
||||
android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/syncadapter_pop_imap" />
|
||||
|
@ -315,7 +382,8 @@
|
|||
android:name="com.android.exchange.EmailSyncAdapterService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
<action
|
||||
android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/syncadapter_email" />
|
||||
|
@ -326,7 +394,8 @@
|
|||
android:name="com.android.exchange.ContactsSyncAdapterService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
<action
|
||||
android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/syncadapter_contacts" />
|
||||
|
@ -337,7 +406,8 @@
|
|||
android:name="com.android.exchange.CalendarSyncAdapterService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
<action
|
||||
android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/syncadapter_calendar" />
|
||||
|
@ -357,7 +427,8 @@
|
|||
android:enabled="true"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
<action
|
||||
android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
|
@ -365,7 +436,7 @@
|
|||
/>
|
||||
</service>
|
||||
<!--
|
||||
EasAuthenticatorService with the altenative label. Disabled by default,
|
||||
EasAuthenticatorService with the alternative label. Disabled by default,
|
||||
and OneTimeInitializer enables it if the vendor policy tells so.
|
||||
-->
|
||||
<service
|
||||
|
@ -374,7 +445,8 @@
|
|||
android:enabled="false"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
<action
|
||||
android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
|
@ -395,7 +467,7 @@
|
|||
it exposes user passwords and other confidential information. -->
|
||||
<provider
|
||||
android:name=".provider.EmailProvider"
|
||||
android:authorities="com.android.email.provider"
|
||||
android:authorities="com.android.email.provider; com.android.email.notifier"
|
||||
android:multiprocess="true"
|
||||
android:permission="com.android.email.permission.ACCESS_PROVIDER"
|
||||
android:label="@string/app_name"
|
||||
|
@ -408,9 +480,27 @@
|
|||
android:readPermission="android.permission.READ_CONTACTS"
|
||||
android:multiprocess="false"
|
||||
>
|
||||
<meta-data android:name="android.content.ContactDirectory" android:value="true"/>
|
||||
<meta-data
|
||||
android:name="android.content.ContactDirectory"
|
||||
android:value="true"/>
|
||||
</provider>
|
||||
<!--EXCHANGE-REMOVE-SECTION-END-->
|
||||
|
||||
<!-- Email AppWidget definitions -->
|
||||
<service
|
||||
android:name=".provider.WidgetProvider$WidgetService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
/>
|
||||
<receiver
|
||||
android:name=".provider.WidgetProvider" >
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info" />
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2010 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:drawable="@drawable/widget_bg" />
|
||||
<item android:state_pressed="true" android:drawable="@drawable/widget_bg_press" />
|
||||
<item android:state_focused="true" android:drawable="@drawable/widget_bg_focus" />
|
||||
<item android:drawable="@drawable/widget_bg" />
|
||||
</selector>
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2010 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.
|
||||
-->
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/widget_foo"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_margin="5dip"
|
||||
android:background="@drawable/widget_background"
|
||||
android:orientation="vertical">
|
||||
<RelativeLayout
|
||||
android:id="@+id/widget_header"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="60dip"
|
||||
android:background="@drawable/widget_bg_top"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
android:id="@+id/widget_logo"
|
||||
android:layout_width="60dip"
|
||||
android:layout_height="60dip"
|
||||
android:layout_marginLeft="5dip"
|
||||
android:layout_marginRight="2dip"
|
||||
android:layout_marginBottom="5dip"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:src="@mipmap/icon"/>
|
||||
<ImageButton
|
||||
android:id="@+id/widget_compose"
|
||||
android:layout_width="56dip"
|
||||
android:layout_height="56dip"
|
||||
android:layout_marginRight="5dip"
|
||||
android:layout_marginTop="3dip"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:src="@drawable/ic_menu_compose"/>
|
||||
<TextView
|
||||
android:id="@+id/widget_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/widget_logo"
|
||||
android:layout_marginTop="10dip"
|
||||
android:textSize="18sp"
|
||||
android:textColor="#F000"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/widget_unread" />
|
||||
</RelativeLayout>
|
||||
<ListView
|
||||
android:id="@+id/message_list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_below="@id/widget_header"
|
||||
android:cacheColorHint="#00000000"
|
||||
/>
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2010 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.
|
||||
-->
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/widget_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/widget_date"
|
||||
android:layout_width="64dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:textColor="#F000"
|
||||
android:textSize="14sp"
|
||||
android:paddingLeft="10dip"
|
||||
android:drawablePadding="4dip"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/widget_from"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_toLeftOf="@id/widget_date"
|
||||
android:textColor="#F000"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textSize="16sp"
|
||||
android:paddingLeft="10dip"
|
||||
android:drawablePadding="4dip"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/widget_subject"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/widget_from"
|
||||
android:minLines="2"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#F000"
|
||||
android:paddingLeft="10dip"
|
||||
/>
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2010 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="50dip"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/loading_text"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:gravity="center"
|
||||
android:textColor="#60FFFFFF"
|
||||
android:textSize="20sp"
|
||||
android:shadowColor="#FF000000"
|
||||
android:shadowDx="0.0"
|
||||
android:shadowDy="1.0"
|
||||
android:shadowRadius="2.0" />
|
||||
</LinearLayout>
|
|
@ -1012,6 +1012,19 @@ save attachment.</string>
|
|||
<!-- Generic string for "current position" / "total number" [CHAR LIMIT=12] -->
|
||||
<string name="position_of_count"><xliff:g example="1">%1$d</xliff:g> of <xliff:g
|
||||
example="12">%2$s</xliff:g></string>
|
||||
|
||||
<!-- Widget -->
|
||||
<!-- Instruction for how to move to different widget views [CHAR LIMIT=50] -->
|
||||
<string name="widget_other_views">Tap email icon for other views</string>
|
||||
<!-- Header for the "All Mail" widget view (showing all of the user's mail) [CHAR LIMIT=20] -->
|
||||
<string name="widget_all_mail">All Mail</string>
|
||||
<!-- Header for the "Unread" widget view (showing all unread mail) [CHAR LIMIT=20] -->
|
||||
<string name="widget_unread">All Unread</string>
|
||||
<!-- Header for the "Starred" widget view (showing all starred mail) [CHAR LIMIT=20] -->
|
||||
<string name="widget_starred">All Starred</string>
|
||||
<!-- Shown when waiting for mail data to be loaded into the widget list view [CHAR LIMIT=20] -->
|
||||
<string name="widget_loading">Loading\u2026</string>
|
||||
|
||||
<!--
|
||||
Strings for temporary UI
|
||||
STOPSHIP Remove them or move them up and make translatable
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2010 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.
|
||||
-->
|
||||
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="220dp"
|
||||
android:minHeight="220dp"
|
||||
android:updatePeriodMillis="0"
|
||||
android:initialLayout="@layout/widget" >
|
||||
</appwidget-provider>
|
|
@ -62,13 +62,17 @@ public class MessageView extends MessageViewBase implements View.OnClickListener
|
|||
* @param mailboxId identifies the sequence of messages used for newer/older navigation.
|
||||
*/
|
||||
public static void actionView(Context context, long messageId, long mailboxId) {
|
||||
context.startActivity(getActionViewIntent(context, messageId, mailboxId));
|
||||
}
|
||||
|
||||
public static Intent getActionViewIntent(Context context, long messageId, long mailboxId) {
|
||||
if (messageId < 0) {
|
||||
throw new IllegalArgumentException("MessageView invalid messageId " + messageId);
|
||||
}
|
||||
Intent i = new Intent(context, MessageView.class);
|
||||
i.putExtra(EXTRA_MESSAGE_ID, messageId);
|
||||
i.putExtra(EXTRA_MAILBOX_ID, mailboxId);
|
||||
context.startActivity(i);
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -63,13 +63,16 @@ import java.util.UUID;
|
|||
*/
|
||||
public abstract class EmailContent {
|
||||
public static final String AUTHORITY = EmailProvider.EMAIL_AUTHORITY;
|
||||
public static final String NOTIFIER_AUTHORITY = EmailProvider.EMAIL_NOTIFIER_AUTHORITY;
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
|
||||
public static final String PARAMETER_LIMIT = "limit";
|
||||
|
||||
public static final Uri CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
|
||||
|
||||
// All classes share this
|
||||
public static final String RECORD_ID = "_id";
|
||||
|
||||
private static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
|
||||
public static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
|
||||
|
||||
/**
|
||||
* This projection can be used with any of the EmailContent classes, when all you need
|
||||
|
@ -448,7 +451,6 @@ public abstract class EmailContent {
|
|||
public static final String DELETED_TABLE_NAME = "Message_Deletes";
|
||||
|
||||
// To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
|
||||
@SuppressWarnings("hiding")
|
||||
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
|
||||
public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
|
||||
public static final Uri SYNCED_CONTENT_URI =
|
||||
|
@ -457,6 +459,8 @@ public abstract class EmailContent {
|
|||
Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
|
||||
public static final Uri UPDATED_CONTENT_URI =
|
||||
Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
|
||||
public static final Uri NOTIFIER_URI =
|
||||
Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message");
|
||||
|
||||
public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
|
||||
|
||||
|
@ -925,7 +929,6 @@ public abstract class EmailContent {
|
|||
|
||||
public static final class Account extends EmailContent implements AccountColumns, Parcelable {
|
||||
public static final String TABLE_NAME = "Account";
|
||||
@SuppressWarnings("hiding")
|
||||
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
|
||||
public static final Uri ADD_TO_FIELD_URI =
|
||||
Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField");
|
||||
|
|
|
@ -37,6 +37,7 @@ import android.accounts.AccountManager;
|
|||
import android.content.ContentProvider;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderResult;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
|
@ -116,6 +117,10 @@ public class EmailProvider extends ContentProvider {
|
|||
public static final int BODY_DATABASE_VERSION = 6;
|
||||
|
||||
public static final String EMAIL_AUTHORITY = "com.android.email.provider";
|
||||
// The notifier authority is used to send notifications regarding changes to messages (insert,
|
||||
// delete, or update) and is intended as an optimization for use by clients of message list
|
||||
// cursors (initially, the email AppWidget).
|
||||
public static final String EMAIL_NOTIFIER_AUTHORITY = "com.android.email.notifier";
|
||||
|
||||
private static final int ACCOUNT_BASE = 0;
|
||||
private static final int ACCOUNT = ACCOUNT_BASE;
|
||||
|
@ -911,6 +916,7 @@ public class EmailProvider extends ContentProvider {
|
|||
int table = match >> BASE_SHIFT;
|
||||
String id = "0";
|
||||
boolean messageDeletion = false;
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
if (Email.LOGD) {
|
||||
Log.v(TAG, "EmailProvider.delete: uri=" + uri + ", match is " + match);
|
||||
|
@ -940,6 +946,7 @@ public class EmailProvider extends ContentProvider {
|
|||
// Bodies are auto-deleted here; Attachments are auto-deleted via trigger
|
||||
messageDeletion = true;
|
||||
db.beginTransaction();
|
||||
resolver.notifyChange(Message.NOTIFIER_URI, null);
|
||||
break;
|
||||
}
|
||||
switch (match) {
|
||||
|
@ -1045,7 +1052,7 @@ public class EmailProvider extends ContentProvider {
|
|||
}
|
||||
|
||||
// Notify all existing cursors.
|
||||
getContext().getContentResolver().notifyChange(EmailContent.CONTENT_URI, null);
|
||||
resolver.notifyChange(EmailContent.CONTENT_URI, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1101,6 +1108,8 @@ public class EmailProvider extends ContentProvider {
|
|||
if (Email.DEBUG_THREAD_CHECK) Email.warnIfUiThread();
|
||||
int match = sURIMatcher.match(uri);
|
||||
Context context = getContext();
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
// See the comment at delete(), above
|
||||
SQLiteDatabase db = getDatabase(context);
|
||||
int table = match >> BASE_SHIFT;
|
||||
|
@ -1121,10 +1130,12 @@ public class EmailProvider extends ContentProvider {
|
|||
|
||||
try {
|
||||
switch (match) {
|
||||
case MESSAGE:
|
||||
resolver.notifyChange(Message.NOTIFIER_URI, null);
|
||||
//$FALL-THROUGH$
|
||||
case UPDATED_MESSAGE:
|
||||
case DELETED_MESSAGE:
|
||||
case BODY:
|
||||
case MESSAGE:
|
||||
case ATTACHMENT:
|
||||
case MAILBOX:
|
||||
case ACCOUNT:
|
||||
|
@ -1175,7 +1186,7 @@ public class EmailProvider extends ContentProvider {
|
|||
}
|
||||
|
||||
// Notify all existing cursors.
|
||||
getContext().getContentResolver().notifyChange(EmailContent.CONTENT_URI, null);
|
||||
resolver.notifyChange(EmailContent.CONTENT_URI, null);
|
||||
return resultUri;
|
||||
}
|
||||
|
||||
|
@ -1360,6 +1371,7 @@ public class EmailProvider extends ContentProvider {
|
|||
|
||||
int match = sURIMatcher.match(uri);
|
||||
Context context = getContext();
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
// See the comment at delete(), above
|
||||
SQLiteDatabase db = getDatabase(context);
|
||||
int table = match >> BASE_SHIFT;
|
||||
|
@ -1411,10 +1423,12 @@ public class EmailProvider extends ContentProvider {
|
|||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
break;
|
||||
case BODY_ID:
|
||||
case MESSAGE_ID:
|
||||
case SYNCED_MESSAGE_ID:
|
||||
resolver.notifyChange(Message.NOTIFIER_URI, null);
|
||||
//$FALL-THROUGH$
|
||||
case UPDATED_MESSAGE_ID:
|
||||
case MESSAGE_ID:
|
||||
case BODY_ID:
|
||||
case ATTACHMENT_ID:
|
||||
case MAILBOX_ID:
|
||||
case ACCOUNT_ID:
|
||||
|
@ -1490,7 +1504,7 @@ public class EmailProvider extends ContentProvider {
|
|||
throw e;
|
||||
}
|
||||
|
||||
context.getContentResolver().notifyChange(notificationUri, null);
|
||||
resolver.notifyChange(notificationUri, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,577 @@
|
|||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
package com.android.email.provider;
|
||||
|
||||
import com.android.email.Email;
|
||||
import com.android.email.R;
|
||||
import com.android.email.activity.MessageCompose;
|
||||
import com.android.email.activity.MessageView;
|
||||
import com.android.email.data.ThrottlingCursorLoader;
|
||||
import com.android.email.provider.EmailContent.Message;
|
||||
import com.android.email.provider.EmailContent.MessageColumns;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.net.Uri.Builder;
|
||||
import android.os.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextUtils.TruncateAt;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.RemoteViewsService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class WidgetProvider extends AppWidgetProvider {
|
||||
private static final String TAG = "WidgetProvider";
|
||||
|
||||
/**
|
||||
* When handling clicks in a widget ListView, a single PendingIntent template is provided to
|
||||
* RemoteViews, and the individual "on click" actions are distinguished via a "fillInIntent"
|
||||
* on each list element; when a click is received, this "fillInIntent" is merged with the
|
||||
* PendingIntent using Intent.fillIn(). Since this mechanism does NOT preserve the Extras
|
||||
* Bundle, we instead encode information about the action (e.g. view, reply, etc.) and its
|
||||
* arguments (e.g. messageId, mailboxId, etc.) in an Uri which is added to the Intent via
|
||||
* Intent.setDataAndType()
|
||||
*
|
||||
* The mime type MUST be set in the Intent, even though we do not use it; therefore, it's value
|
||||
* is entirely arbitrary.
|
||||
*
|
||||
* Our "command" Uri is NOT used by the system in any manner, and is therefore constrained only
|
||||
* in the requirement that it be syntactically valid.
|
||||
*
|
||||
* We use the following convention for our commands:
|
||||
* widget://command/<command>/<arg1>[/<arg2>]
|
||||
*/
|
||||
private static final String WIDGET_DATA_MIME_TYPE = "com.android.email/widget_data";
|
||||
private static final Uri COMMAND_URI = Uri.parse("widget://command");
|
||||
|
||||
// Command names and Uri's built upon COMMAND_URI
|
||||
private static final String COMMAND_NAME_SWITCH_LIST_VIEW = "switch_list_view";
|
||||
private static final Uri COMMAND_URI_SWITCH_LIST_VIEW =
|
||||
COMMAND_URI.buildUpon().appendPath(COMMAND_NAME_SWITCH_LIST_VIEW).build();
|
||||
private static final String COMMAND_NAME_VIEW_MESSAGE = "view_message";
|
||||
private static final Uri COMMAND_URI_VIEW_MESSAGE =
|
||||
COMMAND_URI.buildUpon().appendPath(COMMAND_NAME_VIEW_MESSAGE).build();
|
||||
|
||||
private static final int TOTAL_COUNT_UNKNOWN = -1;
|
||||
private static final int MAX_MESSAGE_LIST_COUNT = 25;
|
||||
|
||||
private static final String SORT_DESCENDING = MessageColumns.TIMESTAMP + " DESC";
|
||||
|
||||
// Map holding our instantiated widgets, accessed by widget id
|
||||
private static HashMap<Integer, EmailWidget> sWidgetMap = new HashMap<Integer, EmailWidget>();
|
||||
private static AppWidgetManager sWidgetManager;
|
||||
private static Context sContext;
|
||||
private static ContentResolver sResolver;
|
||||
private static TextPaint sDatePaint = new TextPaint();
|
||||
|
||||
/**
|
||||
* Types of views that we're prepared to show in the widget - all mail, unread mail, and starred
|
||||
* mail; we rotate between them. Each ViewType is composed of a selection string and a title.
|
||||
*/
|
||||
public enum ViewType {
|
||||
ALL_MAIL(null, R.string.widget_all_mail),
|
||||
UNREAD(MessageColumns.FLAG_READ + "=0", R.string.widget_unread),
|
||||
STARRED(MessageColumns.FLAG_FAVORITE + "=1", R.string.widget_starred);
|
||||
|
||||
private final String selection;
|
||||
private final int titleResource;
|
||||
private String title;
|
||||
|
||||
ViewType(String _selection, int _titleResource) {
|
||||
selection = _selection;
|
||||
titleResource = _titleResource;
|
||||
}
|
||||
|
||||
public String getTitle(Context context) {
|
||||
if (title == null) {
|
||||
title = context.getString(titleResource);
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
static class EmailWidget implements RemoteViewsService.RemoteViewsFactory {
|
||||
// The widget identifier
|
||||
private final int mWidgetId;
|
||||
|
||||
// The cursor underlying the message list for this widget; this must only be modified while
|
||||
// holding mCursorLock
|
||||
private volatile Cursor mCursor;
|
||||
// A lock on our cursor, which is used in the UI thread while inflating views, and by
|
||||
// our Loader in the background
|
||||
private final Object mCursorLock = new Object();
|
||||
// Number of records in the cursor
|
||||
private int mCursorCount = TOTAL_COUNT_UNKNOWN;
|
||||
// The widget's loader (derived from ThrottlingCursorLoader)
|
||||
private WidgetLoader mLoader;
|
||||
|
||||
// The current view type (all mail, unread, or starred for now)
|
||||
private ViewType mViewType = ViewType.ALL_MAIL;
|
||||
|
||||
// The projection to be used by the WidgetLoader
|
||||
public static final String[] WIDGET_PROJECTION = new String[] {
|
||||
EmailContent.RECORD_ID, MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
|
||||
MessageColumns.SUBJECT, MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE,
|
||||
MessageColumns.FLAG_ATTACHMENT, MessageColumns.MAILBOX_KEY, MessageColumns.SNIPPET,
|
||||
MessageColumns.ACCOUNT_KEY
|
||||
};
|
||||
public static final int WIDGET_COLUMN_ID = 0;
|
||||
public static final int WIDGET_COLUMN_DISPLAY_NAME = 1;
|
||||
public static final int WIDGET_COLUMN_TIMESTAMP = 2;
|
||||
public static final int WIDGET_COLUMN_SUBJECT = 3;
|
||||
public static final int WIDGET_COLUMN_FLAG_READ = 4;
|
||||
public static final int WIDGET_COLUMN_FLAG_FAVORITE = 5;
|
||||
public static final int WIDGET_COLUMN_FLAG_ATTACHMENT = 6;
|
||||
public static final int WIDGET_COLUMN_MAILBOX_KEY = 7;
|
||||
public static final int WIDGET_COLUMN_SNIPPET = 8;
|
||||
public static final int WIDGET_COLUMN_ACCOUNT_KEY = 9;
|
||||
|
||||
public EmailWidget(int _widgetId) {
|
||||
super();
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "Creating EmailWidget with id = " + _widgetId);
|
||||
}
|
||||
mWidgetId = _widgetId;
|
||||
mLoader = new WidgetLoader();
|
||||
if (sDatePaint == null) {
|
||||
sDatePaint = new TextPaint();
|
||||
sDatePaint.setTypeface(Typeface.DEFAULT);
|
||||
sDatePaint.setTextSize(14);
|
||||
sDatePaint.setAntiAlias(true);
|
||||
sDatePaint.setTextAlign(Align.RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The ThrottlingCursorLoader does all of the heavy lifting in managing the data loading
|
||||
* task; all we need is to register a listener so that we're notified when the load is
|
||||
* complete.
|
||||
*/
|
||||
final class WidgetLoader extends ThrottlingCursorLoader {
|
||||
protected WidgetLoader() {
|
||||
super(sContext, Message.CONTENT_URI, WIDGET_PROJECTION, mViewType.selection, null,
|
||||
SORT_DESCENDING);
|
||||
registerListener(0, new OnLoadCompleteListener<Cursor>() {
|
||||
@Override
|
||||
public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
|
||||
synchronized (mCursorLock) {
|
||||
// Save away the cursor
|
||||
mCursor = cursor;
|
||||
// Reset the notification Uri to our Message table notifier URI
|
||||
mCursor.setNotificationUri(sResolver, Message.NOTIFIER_URI);
|
||||
// Save away the count (for display)
|
||||
mCursorCount = mCursor.getCount();
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "onLoadComplete, count = " + cursor.getCount());
|
||||
}
|
||||
}
|
||||
RemoteViews views =
|
||||
new RemoteViews(sContext.getPackageName(), R.layout.widget);
|
||||
views.setTextViewText(R.id.widget_title,
|
||||
mViewType.getTitle(sContext) + " (" + mCursorCount + ")");
|
||||
sWidgetManager.partiallyUpdateAppWidget(mWidgetId, views);
|
||||
sWidgetManager.notifyAppWidgetViewDataChanged(mWidgetId, R.id.message_list);
|
||||
}
|
||||
});
|
||||
startLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that stops existing loading (if any), sets a (possibly new)
|
||||
* selection criterion, and starts loading
|
||||
*
|
||||
* @param selection a valid query selection argument
|
||||
*/
|
||||
void startLoadingWithSelection(String selection) {
|
||||
stopLoading();
|
||||
setSelection(selection);
|
||||
startLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the next widget view (cycles all -> unread -> starred)
|
||||
*/
|
||||
public void switchToNextView() {
|
||||
switch(mViewType) {
|
||||
case ALL_MAIL:
|
||||
mViewType = ViewType.UNREAD;
|
||||
break;
|
||||
case UNREAD:
|
||||
mViewType = ViewType.STARRED;
|
||||
break;
|
||||
case STARRED:
|
||||
mViewType = ViewType.ALL_MAIL;
|
||||
break;
|
||||
}
|
||||
synchronized(mCursorLock) {
|
||||
mCursorCount = TOTAL_COUNT_UNKNOWN;
|
||||
invalidateCursorLocked();
|
||||
mLoader.startLoadingWithSelection(mViewType.selection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the current cursor and tells the UI that the underlying data has changed.
|
||||
* This method must be called while holding mCursorLock
|
||||
*/
|
||||
private void invalidateCursorLocked() {
|
||||
mCursor = null;
|
||||
sWidgetManager.notifyAppWidgetViewDataChanged(mWidgetId, R.id.message_list);
|
||||
}
|
||||
|
||||
private void setStyleSpan(SpannableString str, int typeface) {
|
||||
int length = str.length();
|
||||
str.setSpan(new StyleSpan(typeface), 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
private CharSequence formattedText(String str, int typeface) {
|
||||
if (str == null) {
|
||||
return "";
|
||||
}
|
||||
SpannableString ss = new SpannableString(str);
|
||||
setStyleSpan(ss, typeface);
|
||||
return ss;
|
||||
}
|
||||
|
||||
private CharSequence formattedTextFromCursor(Cursor c, int column, int typeface) {
|
||||
return formattedText(mCursor.getString(column), typeface);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for creating an onClickPendingIntent that executes a command via
|
||||
* our command Uri. Used for the "next view" command; appends the widget id to the command
|
||||
* Uri.
|
||||
*
|
||||
* @param views The RemoteViews we're inflating
|
||||
* @param buttonId the id of the button view
|
||||
* @param data the command Uri
|
||||
*/
|
||||
private void setCommandIntent(RemoteViews views, int buttonId, Uri data) {
|
||||
Intent intent = new Intent(sContext, WidgetService.class);
|
||||
intent.setDataAndType(ContentUris.withAppendedId(data, mWidgetId),
|
||||
WIDGET_DATA_MIME_TYPE);
|
||||
PendingIntent pendingIntent = PendingIntent.getService(sContext, 0, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
views.setOnClickPendingIntent(buttonId, pendingIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for creating an onClickPendingIntent that launches another activity
|
||||
* directly. Used for the "Compose" button
|
||||
*
|
||||
* @param views The RemoteViews we're inflating
|
||||
* @param buttonId the id of the button view
|
||||
* @param activityClass the class of the activity to be launched
|
||||
*/
|
||||
private void setActivityIntent(RemoteViews views, int buttonId,
|
||||
Class<? extends Activity> activityClass) {
|
||||
Intent intent = new Intent(sContext, activityClass);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(sContext, 0, intent, 0);
|
||||
views.setOnClickPendingIntent(buttonId, pendingIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for constructing a fillInIntent for a given list view element.
|
||||
* Appends the command and any arguments to a base Uri.
|
||||
*
|
||||
* @param views the RemoteViews we are inflating
|
||||
* @param viewId the id of the view
|
||||
* @param baseUri the base uri for the command
|
||||
* @param args any arguments to the command
|
||||
*/
|
||||
private void setFillInIntent(RemoteViews views, int viewId, Uri baseUri, String ... args) {
|
||||
Intent intent = new Intent();
|
||||
Builder builder = baseUri.buildUpon();
|
||||
for (String arg: args) {
|
||||
builder.appendPath(arg);
|
||||
}
|
||||
intent.setDataAndType(builder.build(), WIDGET_DATA_MIME_TYPE);
|
||||
views.setOnClickFillInIntent(viewId, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the "header" of the widget (i.e. everything that doesn't include the scrolling
|
||||
* message list)
|
||||
*/
|
||||
private void updateHeader() {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "updateWidget " + mWidgetId);
|
||||
}
|
||||
|
||||
// Get the widget layout
|
||||
RemoteViews views = new RemoteViews(sContext.getPackageName(), R.layout.widget);
|
||||
|
||||
// Set up the list with an adapter
|
||||
Intent intent = new Intent(sContext, WidgetService.class);
|
||||
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId);
|
||||
views.setRemoteAdapter(R.id.message_list, intent);
|
||||
|
||||
// Set up the title (view type + count of messages)
|
||||
views.setTextViewText(R.id.widget_title,
|
||||
mViewType.getTitle(sContext) + " (" + mCursorCount + ")");
|
||||
|
||||
// Set up "new" button (compose new message) and "next view" button
|
||||
setActivityIntent(views, R.id.widget_compose, MessageCompose.class);
|
||||
setCommandIntent(views, R.id.widget_logo, COMMAND_URI_SWITCH_LIST_VIEW);
|
||||
|
||||
// Use a bare intent for our template; we need to fill everything in
|
||||
intent = new Intent(sContext, WidgetService.class);
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getService(sContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
views.setPendingIntentTemplate(R.id.message_list, pendingIntent);
|
||||
|
||||
// And finally update the widget
|
||||
sWidgetManager.updateAppWidget(mWidgetId, views);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see android.widget.RemoteViewsService.RemoteViewsFactory#getViewAt(int)
|
||||
*/
|
||||
public RemoteViews getViewAt(int position) {
|
||||
// Use the cursor to set up the widget
|
||||
synchronized (mCursorLock) {
|
||||
if (mCursor == null || !mCursor.moveToPosition(position)) {
|
||||
return getLoadingView();
|
||||
}
|
||||
RemoteViews views =
|
||||
new RemoteViews(sContext.getPackageName(), R.layout.widget_list_item);
|
||||
|
||||
// Typeface for from, subject, and date (normal/bold) depends on whether the message
|
||||
// is read/unread
|
||||
int typeface = (mCursor.getInt(WIDGET_COLUMN_FLAG_READ) == 0) ? Typeface.BOLD
|
||||
: Typeface.NORMAL;
|
||||
views.setTextViewText(R.id.widget_from,
|
||||
formattedTextFromCursor(mCursor, WIDGET_COLUMN_DISPLAY_NAME, typeface));
|
||||
views.setTextViewText(R.id.widget_subject,
|
||||
formattedTextFromCursor(mCursor, WIDGET_COLUMN_SUBJECT, typeface));
|
||||
|
||||
long timestamp = mCursor.getLong(WIDGET_COLUMN_TIMESTAMP);
|
||||
// Get a nicely formatted date string (relative to today)
|
||||
String date = DateUtils.getRelativeTimeSpanString(sContext, timestamp).toString();
|
||||
views.setTextViewText(R.id.widget_date, TextUtils.ellipsize(date, sDatePaint, 64,
|
||||
TruncateAt.END));
|
||||
|
||||
// Set button intents for view, reply, and delete
|
||||
String messageId = mCursor.getString(WIDGET_COLUMN_ID);
|
||||
String mailboxId = mCursor.getString(WIDGET_COLUMN_MAILBOX_KEY);
|
||||
setFillInIntent(views, R.id.widget_message, COMMAND_URI_VIEW_MESSAGE, messageId,
|
||||
mailboxId);
|
||||
|
||||
return views;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (mCursor == null) return 0;
|
||||
return Math.min(mCursor.getCount(), MAX_MESSAGE_LIST_COUNT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteViews getLoadingView() {
|
||||
RemoteViews view = new RemoteViews(sContext.getPackageName(), R.layout.widget_loading);
|
||||
view.setTextViewText(R.id.loading_text, sContext.getString(R.string.widget_loading));
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
// Regular list view and the "loading" view
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataSetChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mLoader != null) {
|
||||
mLoader.stopLoading();
|
||||
}
|
||||
sWidgetMap.remove(mWidgetId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void update(Context context, AppWidgetManager widgetManager,
|
||||
int[] appWidgetIds) {
|
||||
for (int widgetId: appWidgetIds) {
|
||||
getOrCreateWidget(widgetId).updateHeader();
|
||||
}
|
||||
}
|
||||
|
||||
private static EmailWidget getOrCreateWidget(int widgetId) {
|
||||
EmailWidget widget = sWidgetMap.get(widgetId);
|
||||
if (widget == null) {
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "Creating EmailWidget for id #" + widgetId);
|
||||
}
|
||||
widget = new EmailWidget(widgetId);
|
||||
sWidgetMap.put(widgetId, widget);
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisabled(Context context) {
|
||||
super.onDisabled(context);
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "onDisabled");
|
||||
}
|
||||
context.stopService(new Intent(context, WidgetService.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnabled(final Context context) {
|
||||
super.onEnabled(context);
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "onEnabled");
|
||||
}
|
||||
context.startService(new Intent(context, WidgetService.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras != null) {
|
||||
final int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
|
||||
if (appWidgetIds != null && appWidgetIds.length > 0) {
|
||||
if (sWidgetManager == null) {
|
||||
sWidgetManager = AppWidgetManager.getInstance(context);
|
||||
sContext = context.getApplicationContext();
|
||||
sResolver = sContext.getContentResolver();
|
||||
}
|
||||
context.startService(new Intent(context, WidgetService.class));
|
||||
update(sContext, sWidgetManager, appWidgetIds);
|
||||
}
|
||||
}
|
||||
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
|
||||
final int widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
|
||||
// Find the widget in the map
|
||||
EmailWidget widget = sWidgetMap.get(widgetId);
|
||||
if (widget != null) {
|
||||
// Stop loading and remove the widget from the map
|
||||
widget.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We use the WidgetService for two purposes:
|
||||
* 1) To provide a widget factory for RemoteViews, and
|
||||
* 2) To process our command Uri's (i.e. take actions on user clicks)
|
||||
*/
|
||||
public static class WidgetService extends RemoteViewsService {
|
||||
@Override
|
||||
public RemoteViewsFactory onGetViewFactory(Intent intent) {
|
||||
// Which widget do we want (nice alliteration, huh?)
|
||||
int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
|
||||
if (widgetId == -1) return null;
|
||||
// Find the existing widget or create it
|
||||
EmailWidget widget = sWidgetMap.get(widgetId);
|
||||
if (widget == null) {
|
||||
throw new IllegalStateException("onGetViewFactory, widget does not exist");
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivity(Intent intent) {
|
||||
// Since we're not calling startActivity from an Activity, we need the new task flag
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
super.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Uri data = intent.getData();
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "Executing: " + data);
|
||||
}
|
||||
if (data == null) return Service.START_NOT_STICKY;
|
||||
List<String> pathSegments = data.getPathSegments();
|
||||
// Our path segments are <command>, <arg1> [, <arg2>]
|
||||
// First, a quick check of Uri validity
|
||||
if (pathSegments.size() < 2) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
String command = pathSegments.get(0);
|
||||
// Ignore unknown action names
|
||||
try {
|
||||
long arg1 = Long.parseLong(pathSegments.get(1));
|
||||
if (COMMAND_NAME_VIEW_MESSAGE.equals(command)) {
|
||||
// "view", <message id>, <mailbox id>
|
||||
Intent i = MessageView.getActionViewIntent(this, arg1,
|
||||
Long.parseLong(pathSegments.get(2)));
|
||||
startActivity(i);
|
||||
} else if (COMMAND_NAME_SWITCH_LIST_VIEW.equals(command)) {
|
||||
// "next_view", <widget id>
|
||||
EmailWidget widget = sWidgetMap.get((int)arg1);
|
||||
if (widget != null) {
|
||||
widget.switchToNextView();
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// Shouldn't happen as we construct all of the Uri's
|
||||
}
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
|
||||
}
|
||||
}
|