Merge "Import translations. DO NOT MERGE" into jb-ub-mail-ur10
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d3ace59..91a8b3c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -113,8 +113,7 @@
</activity>
<activity
android:name=".browse.EmlViewerActivity"
- android:label="@string/app_name"
- android:exported="false">
+ android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/res/drawable/radio_button.xml b/res/drawable/radio_button.xml
new file mode 100644
index 0000000..e9cb939
--- /dev/null
+++ b/res/drawable/radio_button.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 Google Inc.
+ Licensed to 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_checked="true" android:drawable="@drawable/ic_radiobutton_selected" />
+ <item android:drawable="@drawable/ic_radiobutton" />
+</selector>
diff --git a/res/layout/eml_viewer_activity.xml b/res/layout/eml_viewer_activity.xml
index b7394c3..4999b6f 100644
--- a/res/layout/eml_viewer_activity.xml
+++ b/res/layout/eml_viewer_activity.xml
@@ -16,8 +16,9 @@
limitations under the License.
-->
-<WebView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/webview"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-</WebView>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/eml_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/white" >
+</FrameLayout>
diff --git a/res/menu-sw600dp-land/conversation_actions.xml b/res/menu-sw600dp-land/conversation_actions.xml
index f463841..3a74345 100644
--- a/res/menu-sw600dp-land/conversation_actions.xml
+++ b/res/menu-sw600dp-land/conversation_actions.xml
@@ -84,6 +84,11 @@
android:icon="@drawable/ic_menu_folders_holo_light" />
<item
+ android:id="@+id/move_to_inbox"
+ android:showAsAction="never"
+ android:title="@string/menu_move_to_inbox" />
+
+ <item
android:id="@+id/mark_important"
android:showAsAction="never"
android:title="@string/mark_important" />
diff --git a/res/menu/conversation_actions.xml b/res/menu/conversation_actions.xml
index e70d6db..9d63506 100644
--- a/res/menu/conversation_actions.xml
+++ b/res/menu/conversation_actions.xml
@@ -72,6 +72,11 @@
android:showAsAction="never"
android:icon="@drawable/ic_menu_folders_holo_light" />
+ <item
+ android:id="@+id/move_to_inbox"
+ android:showAsAction="never"
+ android:title="@string/menu_move_to_inbox" />
+
<!-- Always available -->
<item
android:id="@+id/mark_important"
diff --git a/res/menu/conversation_list_selection_actions_menu.xml b/res/menu/conversation_list_selection_actions_menu.xml
index f635a4f..b866497 100644
--- a/res/menu/conversation_list_selection_actions_menu.xml
+++ b/res/menu/conversation_list_selection_actions_menu.xml
@@ -78,6 +78,11 @@
android:title="@string/menu_change_folders"
android:icon="@drawable/ic_menu_folders_holo_light" />
+ <item
+ android:id="@+id/move_to_inbox"
+ android:showAsAction="never"
+ android:title="@string/menu_move_to_inbox" />
+
<item android:id="@+id/star"
android:title="@string/add_star"
android:showAsAction="never" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4498192..cb17a11 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -151,6 +151,8 @@
<string name="menu_change_folders">Change folders</string>
<!-- Menu item: moves to folders for selected conversation(s). [CHAR LIMIT = 30] -->
<string name="menu_move_to">Move to</string>
+ <!-- Menu item: moves current or selected conversation(s) to Inbox. [CHAR LIMIT = 30] -->
+ <string name="menu_move_to_inbox">Move to Inbox</string>
<!-- Menu item: manages the folders for this account. [CHAR LIMIT = 30] -->
<string name="menu_manage_folders">Folder settings</string>
<!-- Menu item: report an email was not readable or poorly rendered -->
@@ -874,8 +876,6 @@
<string name="prefDialogTitle_removal_action">Archive & delete actions</string>
<!-- The default value -->
<string translatable="false" name="prefDefault_removal_action">archive</string>
- <!-- Dialog title for the choosing whether to use archive or delete as remove action, displayed the first time the user archives or deletes a message [CHAR LIMIT=150] -->
- <string name="prefDialogTitle_removal_action_first_time">Set archive & delete preference\n(change in General settings)</string>
<!-- Settings screen, Reply to all default setting title [CHAR LIMIT=30] -->
<string name="preferences_default_reply_all_title">Reply all</string>
diff --git a/src/com/android/mail/browse/ConversationCursor.java b/src/com/android/mail/browse/ConversationCursor.java
index 56100a8..a5fda5f 100644
--- a/src/com/android/mail/browse/ConversationCursor.java
+++ b/src/com/android/mail/browse/ConversationCursor.java
@@ -279,19 +279,24 @@
@Override
public Void doInBackground(Void... param) {
- for (int i = mStartPos; i < getCount(); i++) {
- if (isCancelled()) {
- break;
- }
+ try {
+ Utils.traceBeginSection("backgroundCaching");
+ for (int i = mStartPos; i < getCount(); i++) {
+ if (isCancelled()) {
+ break;
+ }
- final UnderlyingRowData rowData = mRowCache.get(i);
- if (rowData.conversation == null) {
- // We are running in a background thread. Set the position to the row
- // we are interested in.
- if (moveToPosition(i)) {
- cacheConversation(new Conversation(UnderlyingCursorWrapper.this));
+ final UnderlyingRowData rowData = mRowCache.get(i);
+ if (rowData.conversation == null) {
+ // We are running in a background thread. Set the position to the row
+ // we are interested in.
+ if (moveToPosition(i)) {
+ cacheConversation(new Conversation(UnderlyingCursorWrapper.this));
+ }
}
}
+ } finally {
+ Utils.traceEndSection();
}
return null;
}
@@ -342,6 +347,7 @@
final UnderlyingRowData[] cache;
final int count;
int numCached = 0;
+ Utils.traceBeginSection("blockingCaching");
if (result != null && super.moveToFirst()) {
count = super.getCount();
cache = new UnderlyingRowData[count];
@@ -420,6 +426,8 @@
" %sms n=%s cached=%s CONV_PRECACHING=%s",
(end-start), count, numCached, ENABLE_CONVERSATION_PRECACHING);
+ Utils.traceEndSection();
+
// If we haven't cached all of the conversations, start a task to do that
if (ENABLE_CONVERSATION_PRECACHING && numCached < count) {
mCacheLoaderTask = new CacheLoaderTask(numCached);
diff --git a/src/com/android/mail/browse/ConversationMessage.java b/src/com/android/mail/browse/ConversationMessage.java
index dbc9b07..e66494f 100644
--- a/src/com/android/mail/browse/ConversationMessage.java
+++ b/src/com/android/mail/browse/ConversationMessage.java
@@ -53,7 +53,6 @@
public ConversationMessage(MimeMessage mimeMessage) throws MessagingException {
super(mimeMessage);
- // TODO - synthesize conversation
}
public void setController(ConversationController controller) {
@@ -61,7 +60,7 @@
}
public Conversation getConversation() {
- return mController.getConversation();
+ return mController != null ? mController.getConversation() : null;
}
/**
diff --git a/src/com/android/mail/browse/ConversationViewAdapter.java b/src/com/android/mail/browse/ConversationViewAdapter.java
index 97e7642..89ccb09 100644
--- a/src/com/android/mail/browse/ConversationViewAdapter.java
+++ b/src/com/android/mail/browse/ConversationViewAdapter.java
@@ -119,7 +119,10 @@
}
- public class MessageHeaderItem extends ConversationOverlayItem {
+ public static class MessageHeaderItem extends ConversationOverlayItem {
+
+ private final ConversationViewAdapter mAdapter;
+
private ConversationMessage mMessage;
// view state variables
@@ -132,7 +135,9 @@
public CharSequence timestampLong;
public CharSequence recipientSummaryText;
- MessageHeaderItem(ConversationMessage message, boolean expanded, boolean showImages) {
+ MessageHeaderItem(ConversationViewAdapter adapter, ConversationMessage message,
+ boolean expanded, boolean showImages) {
+ mAdapter = adapter;
mMessage = message;
mExpanded = expanded;
mShowImages = showImages;
@@ -153,10 +158,11 @@
public View createView(Context context, LayoutInflater inflater, ViewGroup parent) {
final MessageHeaderView v = (MessageHeaderView) inflater.inflate(
R.layout.conversation_message_header, parent, false);
- v.initialize(mDateBuilder, mAccountController, mAddressCache);
- v.setCallbacks(mMessageCallbacks);
- v.setContactInfoSource(mContactInfoSource);
- v.setVeiledMatcher(mMatcher);
+ v.initialize(mAdapter.mDateBuilder, mAdapter.mAccountController,
+ mAdapter.mAddressCache);
+ v.setCallbacks(mAdapter.mMessageCallbacks);
+ v.setContactInfoSource(mAdapter.mContactInfoSource);
+ v.setVeiledMatcher(mAdapter.mMatcher);
return v;
}
@@ -402,16 +408,16 @@
}
public int addMessageHeader(ConversationMessage msg, boolean expanded, boolean showImages) {
- return addItem(new MessageHeaderItem(msg, expanded, showImages));
+ return addItem(new MessageHeaderItem(this, msg, expanded, showImages));
}
public int addMessageFooter(MessageHeaderItem headerItem) {
return addItem(new MessageFooterItem(headerItem));
}
- public MessageHeaderItem newMessageHeaderItem(ConversationMessage message, boolean expanded,
- boolean showImages) {
- return new MessageHeaderItem(message, expanded, showImages);
+ public static MessageHeaderItem newMessageHeaderItem(ConversationViewAdapter adapter,
+ ConversationMessage message, boolean expanded, boolean showImages) {
+ return new MessageHeaderItem(adapter, message, expanded, showImages);
}
public MessageFooterItem newMessageFooterItem(MessageHeaderItem headerItem) {
diff --git a/src/com/android/mail/browse/EmlMessageLoader.java b/src/com/android/mail/browse/EmlMessageLoader.java
new file mode 100644
index 0000000..09e5b6a
--- /dev/null
+++ b/src/com/android/mail/browse/EmlMessageLoader.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 Google Inc.
+ * Licensed to 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
+ *and
+ * 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.mail.browse;
+
+import android.content.AsyncTaskLoader;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.emailcommon.TempDirectory;
+import com.android.emailcommon.internet.MimeMessage;
+import com.android.emailcommon.mail.MessagingException;
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Loader that builds a ConversationMessage from an EML file Uri.
+ */
+public class EmlMessageLoader extends AsyncTaskLoader<ConversationMessage> {
+ private static final String LOG_TAG = LogTag.getLogTag();
+
+ private Uri mEmlFileUri;
+ private ConversationMessage mMessage;
+
+ public EmlMessageLoader(Context context, Uri emlFileUri) {
+ super(context);
+ mEmlFileUri = emlFileUri;
+ }
+
+ @Override
+ public ConversationMessage loadInBackground() {
+ final Context context = getContext();
+ TempDirectory.setTempDirectory(context);
+ final ContentResolver resolver = context.getContentResolver();
+ final InputStream stream;
+ try {
+ stream = resolver.openInputStream(mEmlFileUri);
+ } catch (FileNotFoundException e) {
+ LogUtils.e(LOG_TAG, e, "Could not find eml file at uri: %s", mEmlFileUri);
+ return null;
+ }
+
+ final MimeMessage mimeMessage;
+ final ConversationMessage convMessage;
+ try {
+ mimeMessage = new MimeMessage(stream);
+ convMessage = new ConversationMessage(mimeMessage);
+ } catch (IOException e) {
+ LogUtils.e(LOG_TAG, e, "Could not read eml file");
+ return null;
+ } catch (MessagingException e) {
+ LogUtils.e(LOG_TAG, e, "Error in parsing eml file");
+ return null;
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ return convMessage;
+ }
+
+ /**
+ * Called when there is new data to deliver to the client. The
+ * super class will take care of delivering it; the implementation
+ * here just adds a little more logic.
+ */
+ @Override
+ public void deliverResult(ConversationMessage result) {
+ if (isReset()) {
+ // An async query came in while the loader is stopped. We
+ // don't need the result.
+ if (result != null) {
+ onReleaseResources(result);
+ }
+ }
+ ConversationMessage oldMessage = mMessage;
+ mMessage = result;
+
+ if (isStarted()) {
+ // If the Loader is currently started, we can immediately
+ // deliver its results.
+ super.deliverResult(result);
+ }
+
+ // At this point we can release the resources associated with
+ // 'oldMessage' if needed; now that the new result is delivered we
+ // know that it is no longer in use.
+ if (oldMessage != null && oldMessage != mMessage) {
+ onReleaseResources(oldMessage);
+ }
+ }
+
+ /**
+ * Handles a request to start the Loader.
+ */
+ @Override
+ protected void onStartLoading() {
+ if (mMessage != null) {
+ // If we currently have a result available, deliver it immediately.
+ deliverResult(mMessage);
+ }
+
+ if (takeContentChanged() || mMessage == null) {
+ // If the data has changed since the last time it was loaded
+ // or is not currently available, start a load.
+ forceLoad();
+ }
+ }
+
+ /**
+ * Handles a request to stop the Loader.
+ */
+ @Override protected void onStopLoading() {
+ // Attempt to cancel the current load task if possible.
+ cancelLoad();
+ }
+
+ /**
+ * Handles a request to cancel a load.
+ */
+ @Override
+ public void onCanceled(ConversationMessage result) {
+ super.onCanceled(result);
+
+ // At this point we can release the resources associated with
+ // the message, if needed.
+ if (result != null) {
+ onReleaseResources(result);
+ }
+ }
+
+ /**
+ * Handles a request to completely reset the Loader.
+ */
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+
+ // At this point we can release the resources associated with
+ // the message, if needed.
+ if (mMessage != null) {
+ onReleaseResources(mMessage);
+ mMessage = null;
+ }
+ }
+
+
+ /**
+ * Helper function to take care of releasing resources associated
+ * with an actively loaded data set.
+ */
+ protected void onReleaseResources(ConversationMessage message) {
+ // DO NOTHING
+ }
+}
diff --git a/src/com/android/mail/browse/EmlMessageViewFragment.java b/src/com/android/mail/browse/EmlMessageViewFragment.java
new file mode 100644
index 0000000..13a3e54
--- /dev/null
+++ b/src/com/android/mail/browse/EmlMessageViewFragment.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2013 Google Inc.
+ * Licensed to 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.mail.browse;
+
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.content.Loader;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+
+import com.android.mail.providers.Account;
+import com.android.mail.providers.Address;
+import com.android.mail.ui.AbstractConversationWebViewClient;
+import com.android.mail.ui.ContactLoaderCallbacks;
+import com.android.mail.ui.SecureConversationViewController;
+import com.android.mail.ui.SecureConversationViewControllerCallbacks;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Fragment that is used to view EML files. It is mostly stubs
+ * that calls {@link SecureConversationViewController} to do most
+ * of the rendering work.
+ */
+public class EmlMessageViewFragment extends Fragment
+ implements SecureConversationViewControllerCallbacks,
+ LoaderManager.LoaderCallbacks<ConversationMessage> {
+ private static final String ARG_EML_FILE_URI = "eml_file_uri";
+ private static final String BASE_URI = "x-thread://message/rfc822/";
+ private static final int MESSAGE_LOADER = 0;
+ private static final int CONTACT_LOADER = 1;
+
+ private final Handler mHandler = new Handler();
+
+ private EmlWebViewClient mWebViewClient;
+ private SecureConversationViewController mViewController;
+ private ContactLoaderCallbacks mContactLoaderCallbacks;
+
+ private Uri mEmlFileUri;
+
+ /**
+ * Cache of email address strings to parsed Address objects.
+ * <p>
+ * Remember to synchronize on the map when reading or writing to this cache, because some
+ * instances use it off the UI thread (e.g. from WebView).
+ */
+ protected final Map<String, Address> mAddressCache = Collections.synchronizedMap(
+ new HashMap<String, Address>());
+
+ private class EmlWebViewClient extends AbstractConversationWebViewClient {
+ public EmlWebViewClient(Account account) {
+ super(account);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ mViewController.dismissLoadingStatus();
+
+ final Set<String> emailAddresses = Sets.newHashSet();
+ final List<Address> cacheCopy;
+ synchronized (mAddressCache) {
+ cacheCopy = ImmutableList.copyOf(mAddressCache.values());
+ }
+ for (Address addr : cacheCopy) {
+ emailAddresses.add(addr.getAddress());
+ }
+ final ContactLoaderCallbacks callbacks = getContactInfoSource();
+ callbacks.setSenders(emailAddresses);
+ getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
+ }
+ };
+
+ /**
+ * Creates a new instance of {@link EmlMessageViewFragment},
+ * initialized to display an eml file from the specified {@link Uri}.
+ */
+ public static EmlMessageViewFragment newInstance(Uri emlFileUri) {
+ EmlMessageViewFragment f = new EmlMessageViewFragment();
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_EML_FILE_URI, emlFileUri);
+ f.setArguments(args);
+ return f;
+ }
+
+ /**
+ * Constructor needs to be public to handle orientation changes and activity
+ * lifecycle events.
+ */
+ public EmlMessageViewFragment() {
+ super();
+ }
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ Bundle args = getArguments();
+ mEmlFileUri = args.getParcelable(ARG_EML_FILE_URI);
+
+ mWebViewClient = new EmlWebViewClient(null);
+ mViewController = new SecureConversationViewController(this);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return mViewController.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mWebViewClient.setActivity(getActivity());
+ mViewController.onActivityCreated(savedInstanceState);
+ }
+
+ // Start SecureConversationViewControllerCallbacks
+
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public AbstractConversationWebViewClient getWebViewClient() {
+ return mWebViewClient;
+ }
+
+ @Override
+ public Fragment getFragment() {
+ return this;
+ }
+
+ @Override
+ public void setupConversationHeaderView(ConversationViewHeader headerView) {
+ // DO NOTHING
+ }
+
+ @Override
+ public boolean isViewVisibleToUser() {
+ return true;
+ }
+
+ @Override
+ public ContactLoaderCallbacks getContactInfoSource() {
+ if (mContactLoaderCallbacks == null) {
+ mContactLoaderCallbacks = new ContactLoaderCallbacks(getActivity());
+ }
+ return mContactLoaderCallbacks;
+ }
+
+ @Override
+ public ConversationAccountController getConversationAccountController() {
+ return null;
+ }
+
+ @Override
+ public Map<String, Address> getAddressCache() {
+ return mAddressCache;
+ }
+
+ @Override
+ public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView) {
+ // DO NOTHING
+ }
+
+ @Override
+ public void startMessageLoader() {
+ getLoaderManager().initLoader(MESSAGE_LOADER, null, this);
+ }
+
+ @Override
+ public String getBaseUri() {
+ return BASE_URI;
+ }
+
+ @Override
+ public boolean isViewOnlyMode() {
+ return true;
+ }
+
+ // End SecureConversationViewControllerCallbacks
+
+ // Start LoaderCallbacks
+
+ @Override
+ public Loader<ConversationMessage> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case MESSAGE_LOADER:
+ return new EmlMessageLoader(getActivity(), mEmlFileUri);
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader<ConversationMessage> loader, ConversationMessage data) {
+ mViewController.setSubject(data.subject);
+ mViewController.renderMessage(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<ConversationMessage> loader) {
+ // Do nothing
+ }
+
+ // End LoaderCallbacks
+}
diff --git a/src/com/android/mail/browse/EmlViewerActivity.java b/src/com/android/mail/browse/EmlViewerActivity.java
index a1c33d0..2524b0f 100644
--- a/src/com/android/mail/browse/EmlViewerActivity.java
+++ b/src/com/android/mail/browse/EmlViewerActivity.java
@@ -19,30 +19,21 @@
import android.app.ActionBar;
import android.app.Activity;
-import android.content.ContentResolver;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
import android.content.Intent;
-import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
-import android.webkit.WebView;
-import com.android.emailcommon.TempDirectory;
-import com.android.emailcommon.internet.MimeMessage;
-import com.android.emailcommon.mail.MessagingException;
import com.android.mail.R;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.MimeType;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
public class EmlViewerActivity extends Activity {
private static final String LOG_TAG = LogTag.getLogTag();
- private WebView mWebView;
-
+ private static final String FRAGMENT_TAG = "eml_message_fragment";
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -50,15 +41,21 @@
final ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
- mWebView = (WebView) findViewById(R.id.webview);
final Intent intent = getIntent();
final String action = intent.getAction();
final String type = intent.getType();
if (Intent.ACTION_VIEW.equals(action) &&
- MimeType.EML_ATTACHMENT_CONTENT_TYPES.contains(type)) {
- openEmlFile(intent.getData());
+ MimeType.isEmlMimeType(type)) {
+ final FragmentManager manager = getFragmentManager();
+
+ if (manager.findFragmentByTag(FRAGMENT_TAG) == null) {
+ final FragmentTransaction transaction = manager.beginTransaction();
+ transaction.add(R.id.eml_root,
+ EmlMessageViewFragment.newInstance(intent.getData()), FRAGMENT_TAG);
+ transaction.commit();
+ }
} else {
LogUtils.wtf(LOG_TAG,
"Entered EmlViewerActivity with wrong intent action or type: %s, %s",
@@ -67,32 +64,7 @@
}
}
- private void openEmlFile(Uri uri) {
- TempDirectory.setTempDirectory(this);
- final ContentResolver resolver = getContentResolver();
- final InputStream stream;
- try {
- stream = resolver.openInputStream(uri);
- } catch (FileNotFoundException e) {
- // TODO handle exception
- return;
- }
- final MimeMessage mimeMessage;
- final ConversationMessage convMessage;
- try {
- mimeMessage = new MimeMessage(stream);
- convMessage = new ConversationMessage(mimeMessage);
- } catch (IOException e) {
- // TODO handle exception
- return;
- } catch (MessagingException e) {
- // TODO handle exception
- return;
- }
-
- mWebView.loadDataWithBaseURL("", convMessage.getBodyAsHtml(), "text/html", "utf-8", null);
- }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
diff --git a/src/com/android/mail/browse/MessageAttachmentBar.java b/src/com/android/mail/browse/MessageAttachmentBar.java
index 3c6f4b4..e61fdcf 100644
--- a/src/com/android/mail/browse/MessageAttachmentBar.java
+++ b/src/com/android/mail/browse/MessageAttachmentBar.java
@@ -283,8 +283,17 @@
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+
+ final String contentType = mAttachment.getContentType();
Utils.setIntentDataAndTypeAndNormalize(
- intent, mAttachment.contentUri, mAttachment.getContentType());
+ intent, mAttachment.contentUri, contentType);
+
+ // For EML files, we want to open our dedicated
+ // viewer rather than let any activity open it.
+ if (MimeType.isEmlMimeType(contentType)) {
+ intent.setClass(getContext(), EmlViewerActivity.class);
+ }
+
try {
getContext().startActivity(intent);
} catch (ActivityNotFoundException e) {
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index 1083002..5036818 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -55,6 +55,7 @@
import com.android.mail.preferences.MailPrefs;
import com.android.mail.providers.Account;
import com.android.mail.providers.Address;
+import com.android.mail.providers.Conversation;
import com.android.mail.providers.Folder;
import com.android.mail.providers.Message;
import com.android.mail.providers.UIProvider;
@@ -217,6 +218,8 @@
private VeiledAddressMatcher mVeiledMatcher;
+ private boolean mIsViewOnlyMode = false;
+
public interface MessageHeaderViewCallbacks {
void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight);
@@ -230,6 +233,7 @@
void showExternalResources(String senderRawAddress);
boolean supportsMessageTransforms();
+
String getMessageTransforms(Message msg);
}
@@ -331,7 +335,7 @@
mLeftSpacer.setVisibility(View.VISIBLE);
mRightSpacer.setVisibility(View.VISIBLE);
} else {
- setBackgroundColor(android.R.color.white);
+ setBackgroundColor(getResources().getColor(android.R.color.white));
// scrolling layer does have padding so we don't need spacers
mLeftSpacer.setVisibility(View.GONE);
mRightSpacer.setVisibility(View.GONE);
@@ -368,7 +372,7 @@
}
private Account getAccount() {
- return mAccountController.getAccount();
+ return mAccountController != null ? mAccountController.getAccount() : null;
}
public void bind(MessageHeaderItem headerItem, boolean measureOnly) {
@@ -437,10 +441,14 @@
mStarView.setContentDescription(getResources().getString(
mStarView.isSelected() ? R.string.remove_star : R.string.add_star));
mStarShown = true;
- for (Folder folder : mMessage.getConversation().getRawFolders()) {
- if (folder.isTrash()) {
- mStarShown = false;
- break;
+
+ final Conversation conversation = mMessage.getConversation();
+ if (conversation != null) {
+ for (Folder folder : conversation.getRawFolders()) {
+ if (folder.isTrash()) {
+ mStarShown = false;
+ break;
+ }
}
}
@@ -600,7 +608,18 @@
private void updateChildVisibility() {
// Too bad this can't be done with an XML state list...
- if (isExpanded()) {
+ if (mIsViewOnlyMode) {
+ setMessageDetailsVisibility(VISIBLE);
+
+
+ setChildVisibility(GONE, mReplyButton, mReplyAllButton, mForwardButton,
+ mOverflowButton, mDraftIcon, mEditDraftButton, mStarView,
+ mAttachmentIcon, mUpperDateView);
+ setChildVisibility(VISIBLE, mPhotoView, mPhotoSpacerView,
+ mSenderEmailView);
+
+ setChildMarginRight(mTitleContainerView, 0);
+ } else if (isExpanded()) {
int normalVis, draftVis;
setMessageDetailsVisibility((mIsSnappy) ? GONE : VISIBLE);
@@ -669,8 +688,9 @@
return;
}
- final boolean defaultReplyAll = getAccount().settings.replyBehavior
- == UIProvider.DefaultReplyBehavior.REPLY_ALL;
+ final Account account = getAccount();
+ final boolean defaultReplyAll = (account != null) ? account.settings.replyBehavior
+ == UIProvider.DefaultReplyBehavior.REPLY_ALL : false;
setChildVisibility(defaultReplyAll ? GONE : VISIBLE, mReplyButton);
setChildVisibility(defaultReplyAll ? VISIBLE : GONE, mReplyAllButton);
}
@@ -702,7 +722,8 @@
final String address = email.getAddress();
// Check if the address here is a veiled address. If it is, we need to display an
// alternate layout
- final boolean isVeiledAddress = mVeiledMatcher.isVeiledAddress(address);
+ final boolean isVeiledAddress = mVeiledMatcher != null &&
+ mVeiledMatcher.isVeiledAddress(address);
final String addressShown;
if (isVeiledAddress) {
// Add the warning at the end of the name, and remove the address. The alternate
@@ -797,7 +818,7 @@
final Address email = getAddress(mAddressCache, rawAddrs[i]);
final String emailAddress = email.getAddress();
final String name;
- if (mMatcher.isVeiledAddress(emailAddress)) {
+ if (mMatcher != null && mMatcher.isVeiledAddress(emailAddress)) {
if (TextUtils.isEmpty(email.getName())) {
// Let's write something more readable.
name = mContext.getString(VeiledAddressMatcher.VEILED_SUMMARY_UNKNOWN);
@@ -983,6 +1004,16 @@
return handled;
}
+ /**
+ * Set to true if the user should not be able to perfrom message actions
+ * on the message such as reply/reply all/forward/star/etc.
+ *
+ * Default is false.
+ */
+ public void setViewOnlyMode(boolean isViewOnlyMode) {
+ mIsViewOnlyMode = isViewOnlyMode;
+ }
+
public void setExpandable(boolean expandable) {
mExpandable = expandable;
}
@@ -1194,7 +1225,11 @@
if (mMessageHeaderItem != null) {
mMessageHeaderItem.setShowImages(true);
}
- showImagePromptAlways(false);
+ if (mIsViewOnlyMode) {
+ hideShowImagePrompt();
+ } else {
+ showImagePromptAlways(false);
+ }
break;
case SHOW_IMAGE_PROMPT_ALWAYS:
mMessage.markAlwaysShowImages(getQueryHandler(), 0 /* token */, null /* cookie */);
@@ -1233,8 +1268,10 @@
}
if (!mCollapsedDetailsValid) {
if (mMessageHeaderItem.recipientSummaryText == null) {
+ final Account account = getAccount();
+ final String name = (account != null) ? account.name : "";
mMessageHeaderItem.recipientSummaryText = getRecipientSummaryText(getContext(),
- getAccount().name, mMyName, mTo, mCc, mBcc, mAddressCache, mVeiledMatcher);
+ name, mMyName, mTo, mCc, mBcc, mAddressCache, mVeiledMatcher);
}
((TextView) findViewById(R.id.recipients_summary))
.setText(mMessageHeaderItem.recipientSummaryText);
diff --git a/src/com/android/mail/browse/SelectedConversationsActionMenu.java b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
index 6b05fbe..604be37 100644
--- a/src/com/android/mail/browse/SelectedConversationsActionMenu.java
+++ b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.net.Uri;
+import android.os.AsyncTask;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
@@ -43,14 +44,17 @@
import com.android.mail.ui.ConversationSetObserver;
import com.android.mail.ui.ConversationUpdater;
import com.android.mail.ui.DestructiveAction;
+import com.android.mail.ui.FolderOperation;
import com.android.mail.ui.FolderSelectionDialog;
import com.android.mail.ui.MailActionBarView;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.Utils;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
import java.util.Collection;
+import java.util.List;
/**
* A component that displays a custom view for an {@code ActionBar}'s {@code
@@ -198,6 +202,25 @@
}
}
break;
+ case R.id.move_to_inbox:
+ new AsyncTask<Void, Void, Folder>() {
+ @Override
+ protected Folder doInBackground(final Void... params) {
+ // Get the "move to" inbox
+ return Utils.getFolder(mContext, mAccount.settings.moveToInbox,
+ true /* allowHidden */);
+ }
+
+ @Override
+ protected void onPostExecute(final Folder moveToInbox) {
+ final List<FolderOperation> ops = Lists.newArrayListWithCapacity(1);
+ // Add inbox
+ ops.add(new FolderOperation(moveToInbox, true));
+ mUpdater.assignFolder(ops, mSelectionSet.values(), true,
+ true /* showUndo */, false /* isMoveTo */);
+ }
+ }.execute((Void[]) null);
+ break;
case R.id.mark_important:
markConversationsImportant(true);
break;
@@ -276,7 +299,7 @@
private void destroy(int actionId, final Collection<Conversation> target,
final DestructiveAction action) {
LogUtils.i(LOG_TAG, "About to remove %d converations", target.size());
- mUpdater.delete(actionId, target, action, true, true /* allowDialog */);
+ mUpdater.delete(actionId, target, action, true);
}
/**
@@ -382,13 +405,18 @@
// archive icon if the setting for that is true.
final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
final MenuItem moveTo = menu.findItem(R.id.move_to);
+ final MenuItem moveToInbox = menu.findItem(R.id.move_to_inbox);
final boolean showRemoveFolder = mFolder != null && mFolder.isType(FolderType.DEFAULT)
&& mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
&& !mFolder.isProviderFolder();
final boolean showMoveTo = mFolder != null
&& mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION);
+ final boolean showMoveToInbox = mFolder != null
+ && mFolder.supportsCapability(FolderCapabilities.ALLOWS_MOVE_TO_INBOX);
removeFolder.setVisible(showRemoveFolder);
moveTo.setVisible(showMoveTo);
+ moveToInbox.setVisible(showMoveToInbox);
+
if (mFolder != null && showRemoveFolder) {
removeFolder.setTitle(mActivity.getActivityContext().getString(R.string.remove_folder,
mFolder.name));
diff --git a/src/com/android/mail/providers/Account.java b/src/com/android/mail/providers/Account.java
index 391ffe0..256b30a 100644
--- a/src/com/android/mail/providers/Account.java
+++ b/src/com/android/mail/providers/Account.java
@@ -756,6 +756,7 @@
settings.conversationViewMode);
map.put(AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN,
settings.veiledAddressPattern);
+ map.put(AccountColumns.SettingsColumns.MOVE_TO_INBOX, settings.moveToInbox);
return map;
}
diff --git a/src/com/android/mail/providers/Settings.java b/src/com/android/mail/providers/Settings.java
index 9a8fddd..d07a60e 100644
--- a/src/com/android/mail/providers/Settings.java
+++ b/src/com/android/mail/providers/Settings.java
@@ -89,6 +89,12 @@
public final Uri setupIntentUri;
public final String veiledAddressPattern;
+ /**
+ * The {@link Uri} to use when moving a conversation to the inbox. May
+ * differ from {@link #defaultInbox}.
+ */
+ public final Uri moveToInbox;
+
/** Cached value of hashCode */
private int mHashCode;
@@ -114,6 +120,7 @@
setupIntentUri = Uri.EMPTY;
conversationViewMode = UIProvider.ConversationViewMode.UNDEFINED;
veiledAddressPattern = null;
+ moveToInbox = Uri.EMPTY;
}
public Settings(Parcel inParcel) {
@@ -135,6 +142,7 @@
setupIntentUri = Utils.getValidUri(inParcel.readString());
conversationViewMode = inParcel.readInt();
veiledAddressPattern = inParcel.readString();
+ moveToInbox = Utils.getValidUri(inParcel.readString());
}
public Settings(Cursor cursor) {
@@ -164,6 +172,8 @@
cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONVERSATION_VIEW_MODE));
veiledAddressPattern =
cursor.getString(cursor.getColumnIndex(SettingsColumns.VEILED_ADDRESS_PATTERN));
+ moveToInbox = Utils.getValidUri(
+ cursor.getString(cursor.getColumnIndex(SettingsColumns.MOVE_TO_INBOX)));
}
private Settings(JSONObject json) {
@@ -190,6 +200,7 @@
conversationViewMode = json.optInt(SettingsColumns.CONVERSATION_VIEW_MODE,
UIProvider.ConversationViewMode.UNDEFINED);
veiledAddressPattern = json.optString(SettingsColumns.VEILED_ADDRESS_PATTERN, null);
+ moveToInbox = Utils.getValidUri(json.optString(SettingsColumns.MOVE_TO_INBOX));
}
/**
@@ -232,6 +243,8 @@
json.put(SettingsColumns.SETUP_INTENT_URI, setupIntentUri);
json.put(SettingsColumns.CONVERSATION_VIEW_MODE, conversationViewMode);
json.put(SettingsColumns.VEILED_ADDRESS_PATTERN, veiledAddressPattern);
+ json.put(SettingsColumns.MOVE_TO_INBOX,
+ getNonNull(moveToInbox, sDefault.moveToInbox));
} catch (JSONException e) {
LogUtils.wtf(LOG_TAG, e, "Could not serialize settings");
}
@@ -298,6 +311,7 @@
dest.writeString(((Uri) getNonNull(setupIntentUri, sDefault.setupIntentUri)).toString());
dest.writeInt(conversationViewMode);
dest.writeString(veiledAddressPattern);
+ dest.writeString(((Uri) getNonNull(moveToInbox, sDefault.moveToInbox)).toString());
}
/**
@@ -403,7 +417,8 @@
&& priorityArrowsEnabled == that.priorityArrowsEnabled
&& setupIntentUri == that.setupIntentUri
&& conversationViewMode == that.conversationViewMode
- && TextUtils.equals(veiledAddressPattern, that.veiledAddressPattern));
+ && TextUtils.equals(veiledAddressPattern, that.veiledAddressPattern))
+ && Objects.equal(moveToInbox, that.moveToInbox);
}
@Override
@@ -423,6 +438,6 @@
snapHeaders, replyBehavior, convListIcon, confirmDelete, confirmArchive,
confirmSend, defaultInbox, forceReplyFromDefault, maxAttachmentSize, swipe,
priorityArrowsEnabled, setupIntentUri, conversationViewMode,
- veiledAddressPattern);
+ veiledAddressPattern, moveToInbox);
}
}
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index 34a1355..7fd379d 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -170,6 +170,7 @@
.put(AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN, String.class)
.put(AccountColumns.UPDATE_SETTINGS_URI, String.class)
.put(AccountColumns.ENABLE_MESSAGE_TRANSFORMS, Integer.class)
+ .put(AccountColumns.SettingsColumns.MOVE_TO_INBOX, String.class)
.build();
public static final Map<String, Class<?>> ACCOUNTS_COLUMNS =
@@ -557,6 +558,11 @@
* constants from {@link ConversationViewMode}
*/
public static final String CONVERSATION_VIEW_MODE = "conversation_view_mode";
+ /**
+ * String containing the URI for the inbox conversations should be moved to for this
+ * account.
+ */
+ public static final String MOVE_TO_INBOX = "move_to_inbox";
}
}
@@ -676,9 +682,9 @@
public static final int STARRED = 1 << 7;
/** Any other system label that we do not have a specific name for. */
public static final int OTHER_PROVIDER_FOLDER = 1 << 8;
- /** All mail folder **/
+ /** All mail folder */
public static final int ALL_MAIL = 1 << 9;
- /** Gmail's inbox sections **/
+ /** Gmail's inbox sections */
public static final int INBOX_SECTION = 1 << 10;
}
@@ -753,6 +759,12 @@
* {@link com.android.mail.ui.MultiFoldersSelectionDialog}).
*/
public static final int MULTI_MOVE = 0x8000;
+
+ /**
+ * This flag indicates that a conversation may be moved from this folder into the account's
+ * inbox.
+ */
+ public static final int ALLOWS_MOVE_TO_INBOX = 0x10000;
}
public static final class FolderColumns {
@@ -1945,6 +1957,11 @@
*/
public static final String FORCE_UI_NOTIFICATIONS_QUERY_PARAMETER = "forceUiNotifications";
+ /**
+ * Parameter used to allow returning hidden folders.
+ */
+ public static final String ALLOW_HIDDEN_FOLDERS_QUERY_PARAM = "allowHiddenFolders";
+
public static final String AUTO_ADVANCE_MODE_OLDER = "older";
public static final String AUTO_ADVANCE_MODE_NEWER = "newer";
public static final String AUTO_ADVANCE_MODE_LIST = "list";
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 0dae8c0..d91a450 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -91,7 +91,6 @@
import com.android.mail.providers.UIProvider.ConversationOperations;
import com.android.mail.providers.UIProvider.FolderCapabilities;
import com.android.mail.ui.ActionableToastBar.ActionClickedListener;
-import com.android.mail.ui.RemovalActionPreferenceDialogFragment.RemovalActionPreferenceDialogListener;
import com.android.mail.utils.ContentProviderTask;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
@@ -1316,8 +1315,7 @@
}
case R.id.remove_folder:
delete(R.id.remove_folder, target,
- getDeferredRemoveFolder(target, mFolder, true, isBatch, true), isBatch,
- true /* allowDialog */);
+ getDeferredRemoveFolder(target, mFolder, true, isBatch, true), isBatch);
break;
case R.id.delete: {
final boolean showDialog = (settings != null && settings.confirmDelete);
@@ -1337,34 +1335,29 @@
case R.id.mark_not_important:
if (mFolder != null && mFolder.isImportantOnly()) {
delete(R.id.mark_not_important, target,
- getDeferredAction(R.id.mark_not_important, target, isBatch), isBatch,
- true /* allowDialog */);
+ getDeferredAction(R.id.mark_not_important, target, isBatch), isBatch);
} else {
updateConversation(Conversation.listOf(mCurrentConversation),
ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
}
break;
case R.id.mute:
- delete(R.id.mute, target, getDeferredAction(R.id.mute, target, isBatch), isBatch,
- true /* allowDialog */);
+ delete(R.id.mute, target, getDeferredAction(R.id.mute, target, isBatch), isBatch);
break;
case R.id.report_spam:
delete(R.id.report_spam, target,
- getDeferredAction(R.id.report_spam, target, isBatch), isBatch,
- true /* allowDialog */);
+ getDeferredAction(R.id.report_spam, target, isBatch), isBatch);
break;
case R.id.mark_not_spam:
// Currently, since spam messages are only shown in list with
// other spam messages,
// marking a message not as spam is a destructive action
delete(R.id.mark_not_spam, target,
- getDeferredAction(R.id.mark_not_spam, target, isBatch), isBatch,
- true /* allowDialog */);
+ getDeferredAction(R.id.mark_not_spam, target, isBatch), isBatch);
break;
case R.id.report_phishing:
delete(R.id.report_phishing, target,
- getDeferredAction(R.id.report_phishing, target, isBatch), isBatch,
- true /* allowDialog */);
+ getDeferredAction(R.id.report_phishing, target, isBatch), isBatch);
break;
case android.R.id.home:
onUpPressed();
@@ -1401,6 +1394,25 @@
dialog.show();
}
break;
+ case R.id.move_to_inbox:
+ new AsyncTask<Void, Void, Folder>() {
+ @Override
+ protected Folder doInBackground(final Void... params) {
+ // Get the "move to" inbox
+ return Utils.getFolder(mContext, mAccount.settings.moveToInbox,
+ true /* allowHidden */);
+ }
+
+ @Override
+ protected void onPostExecute(final Folder moveToInbox) {
+ final List<FolderOperation> ops = Lists.newArrayListWithCapacity(1);
+ // Add inbox
+ ops.add(new FolderOperation(moveToInbox, true));
+ assignFolder(ops, Conversation.listOf(mCurrentConversation), true,
+ true /* showUndo */, false /* isMoveTo */);
+ }
+ }.execute((Void[]) null);
+ break;
case R.id.empty_trash:
showEmptyDialog();
break;
@@ -1577,8 +1589,7 @@
// Conversations are neither marked read, nor viewed, and we don't want to show
// the next conversation.
LogUtils.d(LOG_TAG, ". . doing full mark unread");
- markConversationsRead(Collections.singletonList(conv), false, false, false,
- true /* allowDialog */);
+ markConversationsRead(Collections.singletonList(conv), false, false, false);
} else {
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
final ConversationInfo info = ConversationInfo.fromBlob(originalConversationInfo);
@@ -1633,28 +1644,28 @@
mConversationListLoadFinishedCallbacks.add(new LoadFinishedCallback() {
@Override
public void onLoadFinished() {
- markConversationsRead(targets, read, viewed, true, true /* allowDialog */);
+ markConversationsRead(targets, read, viewed, true);
}
});
} else {
// We want to show the next conversation if we are marking unread.
- markConversationsRead(targets, read, viewed, true, true /* allowDialog */);
+ markConversationsRead(targets, read, viewed, true);
}
}
private void markConversationsRead(final Collection<Conversation> targets, final boolean read,
- final boolean markViewed, final boolean showNext, final boolean allowDialog) {
+ final boolean markViewed, final boolean showNext) {
LogUtils.d(LOG_TAG, "performing markConversationsRead");
// Auto-advance if requested and the current conversation is being marked unread
if (showNext && !read) {
final Runnable operation = new Runnable() {
@Override
public void run() {
- markConversationsRead(targets, read, markViewed, showNext, false);
+ markConversationsRead(targets, read, markViewed, showNext);
}
};
- if (!showNextConversation(targets, operation, allowDialog)) {
+ if (!showNextConversation(targets, operation)) {
// This method will be called again if the user selects an autoadvance option
return;
}
@@ -1704,7 +1715,7 @@
*/
@Override
public void showNextConversation(final Collection<Conversation> target) {
- showNextConversation(target, null, true /* allowDialog */);
+ showNextConversation(target, null);
}
/**
@@ -1730,7 +1741,7 @@
* <code>true</code> otherwise.
*/
private boolean showNextConversation(final Collection<Conversation> target,
- final Runnable operation, final boolean allowDialog) {
+ final Runnable operation) {
final int viewMode = mViewMode.getMode();
final boolean currentConversationInView = (viewMode == ViewMode.CONVERSATION
|| viewMode == ViewMode.SEARCH_RESULTS_CONVERSATION)
@@ -1739,7 +1750,7 @@
if (currentConversationInView) {
final int autoAdvanceSetting = mAccount.settings.getAutoAdvanceSetting();
- if (allowDialog && autoAdvanceSetting == AutoAdvance.UNSET && mIsTablet) {
+ if (autoAdvanceSetting == AutoAdvance.UNSET && mIsTablet) {
displayAutoAdvanceDialogAndPerformAction(operation);
return false;
} else {
@@ -1812,46 +1823,6 @@
.show();
}
- private Runnable mRemovalActionDialogRunnable = null;
-
- private void attachRemovalActionDialogListener() {
- final RemovalActionPreferenceDialogFragment fragment =
- (RemovalActionPreferenceDialogFragment) mActivity.getFragmentManager()
- .findFragmentByTag(RemovalActionPreferenceDialogFragment.FRAGMENT_TAG);
-
- if (fragment != null) {
- fragment.setListener(mRemovalActionPreferenceDialogListener);
- }
- }
-
- private final RemovalActionPreferenceDialogListener mRemovalActionPreferenceDialogListener =
- new RemovalActionPreferenceDialogListener() {
- @Override
- public void onDismiss() {
- if (mRemovalActionDialogRunnable != null) {
- mRemovalActionDialogRunnable.run();
- mRemovalActionDialogRunnable = null;
- }
- }
- };
-
- /**
- * Displays a the removal action dialog, and when the user makes a selection, the preference is
- * stored, and the specified operation is run.
- *
- * @return <code>true</code> if the dialog was shown, <code>false</code> otherwise
- */
- private boolean displayRemovalActionDialogAndPerformAction(final Runnable operation) {
- final boolean shown = RemovalActionPreferenceDialogFragment.showIfNecessary(mContext,
- mAccount, mActivity.getFragmentManager(), mRemovalActionPreferenceDialogListener);
-
- if (shown) {
- mRemovalActionDialogRunnable = operation;
- }
-
- return shown;
- }
-
@Override
public void starMessage(ConversationMessage msg, boolean starred) {
if (msg.starred == starred) {
@@ -1921,14 +1892,13 @@
final ConfirmDialogFragment c = ConfirmDialogFragment.newInstance(message);
c.displayDialog(mActivity.getFragmentManager());
} else {
- delete(actionId, target, getDeferredAction(actionId, target, isBatch), isBatch,
- true /* allowDialog */);
+ delete(0, target, getDeferredAction(actionId, target, isBatch), isBatch);
}
}
@Override
public void delete(final int actionId, final Collection<Conversation> target,
- final DestructiveAction action, final boolean isBatch, final boolean allowDialog) {
+ final DestructiveAction action, final boolean isBatch) {
// Order of events is critical! The Conversation View Fragment must be
// notified of the next conversation with showConversation(next) *before* the
// conversation list
@@ -1939,18 +1909,13 @@
final Runnable operation = new Runnable() {
@Override
public void run() {
- delete(actionId, target, action, isBatch, false);
+ delete(actionId, target, action, isBatch);
}
};
- if (!showNextConversation(target, operation, allowDialog)) {
+ if (!showNextConversation(target, operation)) {
// This method will be called again if the user selects an autoadvance option
return;
- } else if (allowDialog && (actionId == R.id.delete || actionId == R.id.archive)
- && displayRemovalActionDialogAndPerformAction(operation)) {
- // Show a dialog to inform the user that they can configure the removal setting
- // This method will be called again when the dialog is dismissed
- return;
}
// If the conversation is in the selected set, remove it from the set.
// Batch selections are cleared in the end of the action, so not done for batch actions.
@@ -2010,7 +1975,6 @@
mSafeToModifyFragments = true;
attachEmptyFolderDialogFragmentListener();
- attachRemovalActionDialogListener();
// Invalidating the options menu so that when we make changes in settings,
// the changes will always be updated in the action bar/options menu/
@@ -2897,7 +2861,7 @@
folderChange = getDeferredFolderChange(target, folderOps, isDestructive,
batch, showUndo, isMoveTo, actionFolder);
- delete(0, target, folderChange, batch, true /* allowDialog */);
+ delete(0, target, folderChange, batch);
} else {
folderChange = getFolderChange(target, folderOps, isDestructive,
batch, showUndo, false /* isMoveTo */, mFolder);
@@ -3176,7 +3140,7 @@
getFolderChange(conversations, dragDropOperations, isDestructive,
true /* isBatch */, true /* showUndo */, true /* isMoveTo */, folder);
if (isDestructive) {
- delete(0, conversations, action, true, true /* allowDialog */);
+ delete(0, conversations, action, true);
} else {
action.performAction();
}
@@ -4034,7 +3998,7 @@
mDialogListener = new AlertDialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- delete(action, target, destructiveAction, isBatch, true /* allowDialog */);
+ delete(action, target, destructiveAction, isBatch);
// Afterwards, let's remove references to the listener and the action.
setListener(null, -1);
}
diff --git a/src/com/android/mail/ui/AbstractConversationViewFragment.java b/src/com/android/mail/ui/AbstractConversationViewFragment.java
index b4bd5fd..29cedef 100644
--- a/src/com/android/mail/ui/AbstractConversationViewFragment.java
+++ b/src/com/android/mail/ui/AbstractConversationViewFragment.java
@@ -30,14 +30,12 @@
import android.view.MenuInflater;
import android.view.MenuItem;
-import com.android.mail.FormattedDateBuilder;
import com.android.mail.R;
import com.android.mail.browse.ConversationAccountController;
import com.android.mail.browse.ConversationMessage;
import com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks;
import com.android.mail.browse.MessageCursor;
import com.android.mail.browse.MessageCursor.ConversationController;
-import com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
import com.android.mail.content.ObjectCursor;
import com.android.mail.content.ObjectCursorLoader;
import com.android.mail.providers.Account;
@@ -59,7 +57,7 @@
public abstract class AbstractConversationViewFragment extends Fragment implements
- ConversationController, ConversationAccountController, MessageHeaderViewCallbacks,
+ ConversationController, ConversationAccountController,
ConversationViewHeaderCallbacks {
private static final String ARG_ACCOUNT = "account";
@@ -70,7 +68,6 @@
protected static final int CONTACT_LOADER = 1;
protected ControllableActivity mActivity;
private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks();
- protected FormattedDateBuilder mDateBuilder;
private ContactLoaderCallbacks mContactLoaderCallbacks;
private MenuItem mChangeFoldersMenuItem;
protected Conversation mConversation;
@@ -82,6 +79,7 @@
* Must be instantiated in a derived class's onCreate.
*/
protected AbstractConversationWebViewClient mWebViewClient;
+
/**
* Cache of email address strings to parsed Address objects.
* <p>
@@ -241,7 +239,7 @@
}
mActivity = (ControllableActivity) activity;
mContext = activity.getApplicationContext();
- mDateBuilder = new FormattedDateBuilder((Context) mActivity);
+ mWebViewClient.setActivity(activity);
mAccount = mAccountObserver.initialize(mActivity.getAccountController());
mWebViewClient.setAccount(mAccount);
}
@@ -331,6 +329,8 @@
mHasConversationBeenTransformed && !mHasConversationTransformBeenReverted);
}
+ abstract boolean supportsMessageTransforms();
+
// BEGIN conversation header callbacks
@Override
public void onFoldersClicked() {
diff --git a/src/com/android/mail/ui/AbstractConversationWebViewClient.java b/src/com/android/mail/ui/AbstractConversationWebViewClient.java
index 139eaa5..0684b0c 100644
--- a/src/com/android/mail/ui/AbstractConversationWebViewClient.java
+++ b/src/com/android/mail/ui/AbstractConversationWebViewClient.java
@@ -17,8 +17,8 @@
package com.android.mail.ui;
+import android.app.Activity;
import android.content.ActivityNotFoundException;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
@@ -45,20 +45,23 @@
private static final String LOG_TAG = LogTag.getLogTag();
private Account mAccount;
- private final Context mContext;
+ private Activity mActivity;
- public AbstractConversationWebViewClient(Context context, Account account) {
+ public AbstractConversationWebViewClient(Account account) {
mAccount = account;
- mContext = context;
}
public void setAccount(Account account) {
mAccount = account;
}
+ public void setActivity(Activity activity) {
+ mActivity = activity;
+ }
+
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
- if (mContext == null) {
+ if (mActivity == null) {
return false;
}
@@ -69,12 +72,12 @@
intent = generateProxyIntent(uri);
} else {
intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName());
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, mActivity.getPackageName());
}
try {
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
- mContext.startActivity(intent);
+ mActivity.startActivity(intent);
result = true;
} catch (ActivityNotFoundException ex) {
// If no application can handle the URL, assume that the
@@ -93,7 +96,7 @@
// We need to catch the exception to make CanvasConversationHeaderView
// test pass. Bug: http://b/issue?id=3470653.
try {
- manager = mContext.getPackageManager();
+ manager = mActivity.getPackageManager();
} catch (UnsupportedOperationException e) {
LogUtils.e(LOG_TAG, e, "Error getting package manager");
}
@@ -103,7 +106,7 @@
final List<ResolveInfo> resolvedActivities = manager.queryIntentActivities(
intent, PackageManager.MATCH_DEFAULT_ONLY);
- final String packageName = mContext.getPackageName();
+ final String packageName = mActivity.getPackageName();
// Now try and find one that came from this package, if one is not found, the UI
// provider must have specified an intent that is to be handled by a different apk.
diff --git a/src/com/android/mail/ui/ConversationUpdater.java b/src/com/android/mail/ui/ConversationUpdater.java
index cb6bad6..c3b71af 100644
--- a/src/com/android/mail/ui/ConversationUpdater.java
+++ b/src/com/android/mail/ui/ConversationUpdater.java
@@ -82,11 +82,10 @@
* @param target the conversations to act upon.
* @param action to perform after the UI has been updated to remove the conversations
* @param isBatch true if this is a batch action, false otherwise.
- * @param allowDialog <code>true</code> to allow dialogs to be displayed
*/
void delete(
int actionId, final Collection<Conversation> target, final DestructiveAction action,
- boolean isBatch, boolean allowDialog);
+ boolean isBatch);
/**
* Mark a number of conversations as read or unread.
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index a110ce2..6de375e 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -17,7 +17,6 @@
package com.android.mail.ui;
-
import android.content.ContentResolver;
import android.content.Context;
import android.content.Loader;
@@ -42,7 +41,6 @@
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
-import android.webkit.WebViewClient;
import android.widget.TextView;
import com.android.mail.FormattedDateBuilder;
@@ -85,13 +83,12 @@
import java.util.Map;
import java.util.Set;
-
/**
* The conversation view UI component.
*/
public final class ConversationViewFragment extends AbstractConversationViewFragment implements
- SuperCollapsedBlock.OnClickListener,
- OnLayoutChangeListener {
+ SuperCollapsedBlock.OnClickListener, OnLayoutChangeListener,
+ MessageHeaderView.MessageHeaderViewCallbacks {
private static final String LOG_TAG = LogTag.getLogTag();
public static final String LAYOUT_TAG = "ConvLayout";
@@ -323,7 +320,7 @@
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
- mWebViewClient = new ConversationWebViewClient(getContext(), mAccount);
+ mWebViewClient = new ConversationWebViewClient(mAccount);
if (savedState != null) {
mWebViewYPercent = savedState.getFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT);
@@ -738,8 +735,8 @@
for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) {
cursor.moveToPosition(i);
final ConversationMessage msg = cursor.getMessage();
- final MessageHeaderItem header = mAdapter.newMessageHeaderItem(msg,
- false /* expanded */, mViewState.getShouldShowImages(msg));
+ final MessageHeaderItem header = ConversationViewAdapter.newMessageHeaderItem(
+ mAdapter, msg, false /* expanded */, mViewState.getShouldShowImages(msg));
final MessageFooterItem footer = mAdapter.newMessageFooterItem(header);
final int headerPx = measureOverlayHeight(header);
@@ -979,8 +976,8 @@
}
private class ConversationWebViewClient extends AbstractConversationWebViewClient {
- public ConversationWebViewClient(Context context, Account account) {
- super(context, account);
+ public ConversationWebViewClient(Account account) {
+ super(account);
}
@Override
@@ -1012,8 +1009,8 @@
for (Address addr : cacheCopy) {
emailAddresses.add(addr.getAddress());
}
- ContactLoaderCallbacks callbacks = getContactInfoSource();
- getContactInfoSource().setSenders(emailAddresses);
+ final ContactLoaderCallbacks callbacks = getContactInfoSource();
+ callbacks.setSenders(emailAddresses);
getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
}
diff --git a/src/com/android/mail/ui/MailActionBarView.java b/src/com/android/mail/ui/MailActionBarView.java
index d7bf719..c967530 100644
--- a/src/com/android/mail/ui/MailActionBarView.java
+++ b/src/com/android/mail/ui/MailActionBarView.java
@@ -894,6 +894,9 @@
&& !mFolder.isProviderFolder());
Utils.setMenuItemVisibility(menu, R.id.move_to, mFolder != null
&& mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION));
+ Utils.setMenuItemVisibility(menu, R.id.move_to_inbox, mFolder != null
+ && mFolder.supportsCapability(FolderCapabilities.ALLOWS_MOVE_TO_INBOX));
+
final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
if (mFolder != null && removeFolder != null) {
removeFolder.setTitle(mActivity.getApplicationContext().getString(
diff --git a/src/com/android/mail/ui/RemovalActionPreferenceDialogFragment.java b/src/com/android/mail/ui/RemovalActionPreferenceDialogFragment.java
deleted file mode 100644
index 8b177a5..0000000
--- a/src/com/android/mail/ui/RemovalActionPreferenceDialogFragment.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2013 Google Inc.
- * Licensed to 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.mail.ui;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.FragmentManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.os.Bundle;
-
-import com.android.mail.R;
-import com.android.mail.preferences.MailPrefs;
-import com.android.mail.providers.Account;
-import com.android.mail.providers.UIProvider.AccountCapabilities;
-
-import java.lang.ref.WeakReference;
-
-/**
- * This dialog is shown when a user first archives or deletes a message, and asks them what there
- * preferred action is (archive, delete, or both).
- */
-public class RemovalActionPreferenceDialogFragment extends DialogFragment {
- /**
- * A listener that can be attached to a {@link RemovalActionPreferenceDialogFragment} to receive
- * onDismiss() events.
- */
- public interface RemovalActionPreferenceDialogListener {
- void onDismiss();
- }
-
- public static final String FRAGMENT_TAG = "ArchiveDeletePreferenceDialogFragment";
-
- private static final String ARG_DEFAULT_VALUE = "defaultValue";
-
- private String mDefaultValue;
-
- private WeakReference<RemovalActionPreferenceDialogListener> mListener = null;
-
- /**
- * Create a new {@link ArchiveDeletePreferenceDialogFragment}.
- *
- * @param defaultValue the initial value to show as checked
- */
- public static RemovalActionPreferenceDialogFragment newInstance(final String defaultValue) {
- final RemovalActionPreferenceDialogFragment fragment =
- new RemovalActionPreferenceDialogFragment();
-
- final Bundle args = new Bundle(1);
- args.putString(ARG_DEFAULT_VALUE, defaultValue);
- fragment.setArguments(args);
-
- return fragment;
- }
-
- public void setListener(final RemovalActionPreferenceDialogListener listener) {
- mListener = new WeakReference<RemovalActionPreferenceDialogListener>(listener);
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final String[] entries = getResources().getStringArray(R.array.prefEntries_removal_action);
- final String[] entryValues =
- getResources().getStringArray(R.array.prefValues_removal_action);
-
- final String defaultValue = getArguments().getString(ARG_DEFAULT_VALUE);
-
- // Find the default value in the entryValues array
- // If we can't find it, we end up on index 0, which is "archive"
- int defaultItem;
- for (defaultItem = entryValues.length - 1; defaultItem >= 0; defaultItem--) {
- if (entryValues[defaultItem].equals(defaultValue)) {
- break;
- }
- }
-
- mDefaultValue = entryValues[defaultItem];
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.prefDialogTitle_removal_action_first_time)
- .setSingleChoiceItems(entries, defaultItem, new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- mDefaultValue = entryValues[which];
- dismiss();
- }
- });
-
- return builder.create();
- }
-
- @Override
- public void onDismiss(final DialogInterface dialog) {
- super.onDismiss(dialog);
-
- // Use whatever was originally selected
- saveRemovalAction(mDefaultValue);
- }
-
- /**
- * Shows the dialog, if necessary.
- *
- * @param account The account this is being requested for
- * @param mRemovalActionPreferenceDialogListener
- * @param defaultValue The default removal action to show checked
- * @return <code>true</code> if the dialog was shown, <code>false</code> otherwise
- */
- public static boolean showIfNecessary(final Context context, final Account account,
- final FragmentManager fragmentManager,
- final RemovalActionPreferenceDialogListener removalActionPreferenceDialogListener) {
- final boolean supportsArchive = account.supportsCapability(AccountCapabilities.ARCHIVE);
-
- if (shouldDisplayDialog(context, supportsArchive)) {
- final String defaultValue = MailPrefs.get(context).getRemovalAction(supportsArchive);
-
- final RemovalActionPreferenceDialogFragment fragment = newInstance(defaultValue);
- fragment.setListener(removalActionPreferenceDialogListener);
- fragment.show(fragmentManager, FRAGMENT_TAG);
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Checks whether we should show the dialog. We show it if:
- * <ol>
- * <li>Archive is supported by the account</li>
- * <li>We have not previously shown the dialog</li>
- * </ol>
- *
- * @param supportsArchive <code>true</code> if the current account supports
- * archive, <code>false</code> otherwise
- * @return <code>true</code> if the dialog needs to be displayed (because it
- * hasn't been shown yet), <code>false</code> otherwise
- */
- private static boolean shouldDisplayDialog(final Context context,
- final boolean supportsArchive) {
- return supportsArchive && !MailPrefs.get(context).hasRemovalActionDialogShown();
- }
-
- private void saveRemovalAction(final String removalAction) {
- final MailPrefs mailPrefs = MailPrefs.get(getActivity());
- mailPrefs.setRemovalAction(removalAction);
- mailPrefs.setRemovalActionDialogShown();
-
- if (mListener != null) {
- final RemovalActionPreferenceDialogListener listener = mListener.get();
- if (listener != null) {
- listener.onDismiss();
- }
- }
- }
-}
diff --git a/src/com/android/mail/ui/SecureConversationViewController.java b/src/com/android/mail/ui/SecureConversationViewController.java
new file mode 100644
index 0000000..a3e6ec8
--- /dev/null
+++ b/src/com/android/mail/ui/SecureConversationViewController.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2013 Google Inc.
+ * Licensed to 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.mail.ui;
+
+import android.app.Fragment;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebSettings;
+
+import com.android.mail.FormattedDateBuilder;
+import com.android.mail.R;
+import com.android.mail.browse.ConversationMessage;
+import com.android.mail.browse.ConversationViewAdapter;
+import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
+import com.android.mail.browse.ConversationViewHeader;
+import com.android.mail.browse.MessageFooterView;
+import com.android.mail.browse.MessageHeaderView;
+import com.android.mail.browse.MessageScrollView;
+import com.android.mail.browse.MessageWebView;
+import com.android.mail.providers.Message;
+import com.android.mail.utils.ConversationViewUtils;
+
+/**
+ * Controller to do most of the heavy lifting for {@link SecureConversationViewFragment}
+ * and {@link com.android.mail.browse.EmlMessageViewFragment}. Currently that work is
+ * pretty much the rendering logic.
+ */
+public class SecureConversationViewController implements
+ MessageHeaderView.MessageHeaderViewCallbacks {
+ private static final String BEGIN_HTML =
+ "<body style=\"margin: 0 %spx;\"><div style=\"margin: 16px 0; font-size: 80%%\">";
+ private static final String END_HTML = "</div></body>";
+
+ private final SecureConversationViewControllerCallbacks mCallbacks;
+
+ private MessageWebView mWebView;
+ private ConversationViewHeader mConversationHeaderView;
+ private MessageHeaderView mMessageHeaderView;
+ private MessageFooterView mMessageFooterView;
+ private ConversationMessage mMessage;
+ private MessageScrollView mScrollView;
+
+ private ConversationViewProgressController mProgressController;
+ private FormattedDateBuilder mDateBuilder;
+
+ private int mSideMarginInWebPx;
+
+ public SecureConversationViewController(SecureConversationViewControllerCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.secure_conversation_view, container, false);
+ mScrollView = (MessageScrollView) rootView.findViewById(R.id.scroll_view);
+ mConversationHeaderView = (ConversationViewHeader) rootView.findViewById(R.id.conv_header);
+ mMessageHeaderView = (MessageHeaderView) rootView.findViewById(R.id.message_header);
+ mMessageFooterView = (MessageFooterView) rootView.findViewById(R.id.message_footer);
+
+ mProgressController = new ConversationViewProgressController(
+ mCallbacks.getFragment(), mCallbacks.getHandler());
+ mProgressController.instantiateProgressIndicators(rootView);
+ mWebView = (MessageWebView) rootView.findViewById(R.id.webview);
+ mWebView.setWebViewClient(mCallbacks.getWebViewClient());
+ mWebView.setFocusable(false);
+ final WebSettings settings = mWebView.getSettings();
+
+ settings.setJavaScriptEnabled(false);
+ settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
+
+ ConversationViewUtils.setTextZoom(mCallbacks.getFragment().getResources(), settings);
+
+ settings.setSupportZoom(true);
+ settings.setBuiltInZoomControls(true);
+ settings.setDisplayZoomControls(false);
+
+ mScrollView.setInnerScrollableView(mWebView);
+
+ return rootView;
+ }
+
+ public void onActivityCreated(Bundle savedInstanceState) {
+ mCallbacks.setupConversationHeaderView(mConversationHeaderView);
+
+ final Fragment fragment = mCallbacks.getFragment();
+
+ mDateBuilder = new FormattedDateBuilder(fragment.getActivity());
+ mMessageHeaderView.initialize(mDateBuilder,
+ mCallbacks.getConversationAccountController(), mCallbacks.getAddressCache());
+ mMessageHeaderView.setExpandMode(MessageHeaderView.POPUP_MODE);
+ mMessageHeaderView.setContactInfoSource(mCallbacks.getContactInfoSource());
+ mMessageHeaderView.setCallbacks(this);
+ mMessageHeaderView.setExpandable(false);
+ mMessageHeaderView.setViewOnlyMode(mCallbacks.isViewOnlyMode());
+
+ mCallbacks.setupMessageHeaderVeiledMatcher(mMessageHeaderView);
+
+ mMessageFooterView.initialize(fragment.getLoaderManager(), fragment.getFragmentManager());
+
+ mCallbacks.startMessageLoader();
+
+ mProgressController.showLoadingStatus(mCallbacks.isViewVisibleToUser());
+
+ final Resources r = mCallbacks.getFragment().getResources();
+ mSideMarginInWebPx = (int) ((r.getDimensionPixelOffset(
+ R.dimen.conversation_view_margin_side) + r.getDimensionPixelOffset(
+ R.dimen.conversation_message_content_margin_side)) / r.getDisplayMetrics().density);
+ }
+
+ /**
+ * Populate the adapter with overlay views (message headers, super-collapsed
+ * blocks, a conversation header), and return an HTML document with spacer
+ * divs inserted for all overlays.
+ */
+ public void renderMessage(ConversationMessage message) {
+ mMessage = message;
+
+ mWebView.getSettings().setBlockNetworkImage(!mMessage.alwaysShowImages);
+
+ // Add formatting to message body
+ // At this point, only adds margins.
+ StringBuilder dataBuilder = new StringBuilder(
+ String.format(BEGIN_HTML, mSideMarginInWebPx));
+ dataBuilder.append(mMessage.getBodyAsHtml());
+ dataBuilder.append(END_HTML);
+
+ mWebView.loadDataWithBaseURL(mCallbacks.getBaseUri(), dataBuilder.toString(),
+ "text/html", "utf-8", null);
+ final MessageHeaderItem item = ConversationViewAdapter.newMessageHeaderItem(
+ null, mMessage, true, mMessage.alwaysShowImages);
+ mMessageHeaderView.bind(item, false);
+ if (mMessage.hasAttachments) {
+ mMessageFooterView.setVisibility(View.VISIBLE);
+ mMessageFooterView.bind(item, false);
+ }
+ }
+
+ public ConversationMessage getMessage() {
+ return mMessage;
+ }
+
+ public ConversationViewHeader getConversationHeaderView() {
+ return mConversationHeaderView;
+ }
+
+ public void dismissLoadingStatus() {
+ mProgressController.dismissLoadingStatus();
+ }
+
+ public void setSubject(String subject) {
+ mConversationHeaderView.setSubject(subject);
+ }
+
+ // Start MessageHeaderViewCallbacks implementations
+
+ @Override
+ public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight) {
+ // Do nothing.
+ }
+
+ @Override
+ public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeight) {
+ // Do nothing.
+ }
+
+ @Override
+ public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightBefore) {
+ // Do nothing.
+ }
+
+ @Override
+ public void showExternalResources(final Message msg) {
+ mWebView.getSettings().setBlockNetworkImage(false);
+ }
+
+ @Override
+ public void showExternalResources(final String rawSenderAddress) {
+ mWebView.getSettings().setBlockNetworkImage(false);
+ }
+
+ @Override
+ public boolean supportsMessageTransforms() {
+ return false;
+ }
+
+ @Override
+ public String getMessageTransforms(final Message msg) {
+ return null;
+ }
+
+ // End MessageHeaderViewCallbacks implementations
+}
diff --git a/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java b/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java
new file mode 100644
index 0000000..06857ed
--- /dev/null
+++ b/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 Google Inc.
+ * Licensed to 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.mail.ui;
+
+import android.app.Fragment;
+import android.os.Handler;
+
+import com.android.mail.ContactInfoSource;
+import com.android.mail.browse.ConversationAccountController;
+import com.android.mail.browse.ConversationViewHeader;
+import com.android.mail.browse.MessageHeaderView;
+import com.android.mail.providers.Address;
+
+import java.util.Map;
+
+/**
+ * Callbacks for fragments that use the {@link SecureConversationViewController}.
+ */
+public interface SecureConversationViewControllerCallbacks {
+ public Handler getHandler();
+ public AbstractConversationWebViewClient getWebViewClient();
+ public Fragment getFragment();
+ public void setupConversationHeaderView(ConversationViewHeader headerView);
+ public boolean isViewVisibleToUser();
+ public ContactInfoSource getContactInfoSource();
+ public ConversationAccountController getConversationAccountController();
+ public Map<String, Address> getAddressCache();
+ public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView);
+ public void startMessageLoader();
+ public String getBaseUri();
+ public boolean isViewOnlyMode();
+}
diff --git a/src/com/android/mail/ui/SecureConversationViewFragment.java b/src/com/android/mail/ui/SecureConversationViewFragment.java
index b94be5d..c3efd59 100644
--- a/src/com/android/mail/ui/SecureConversationViewFragment.java
+++ b/src/com/android/mail/ui/SecureConversationViewFragment.java
@@ -17,60 +17,43 @@
package com.android.mail.ui;
-import android.content.Context;
+import android.app.Fragment;
import android.content.Loader;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.webkit.WebSettings;
-import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import com.android.mail.R;
+import com.android.mail.browse.ConversationAccountController;
import com.android.mail.browse.ConversationMessage;
-import com.android.mail.browse.ConversationViewAdapter;
-import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
import com.android.mail.browse.ConversationViewHeader;
import com.android.mail.browse.MessageCursor;
-import com.android.mail.browse.MessageFooterView;
import com.android.mail.browse.MessageHeaderView;
-import com.android.mail.browse.MessageScrollView;
-import com.android.mail.browse.MessageWebView;
import com.android.mail.content.ObjectCursor;
import com.android.mail.providers.Account;
+import com.android.mail.providers.Address;
import com.android.mail.providers.Conversation;
-import com.android.mail.providers.Message;
-import com.android.mail.utils.ConversationViewUtils;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
-public class SecureConversationViewFragment extends AbstractConversationViewFragment {
+public class SecureConversationViewFragment extends AbstractConversationViewFragment
+ implements SecureConversationViewControllerCallbacks {
private static final String LOG_TAG = LogTag.getLogTag();
- private static final String BEGIN_HTML =
- "<body style=\"margin: 0 %spx;\"><div style=\"margin: 16px 0; font-size: 80%%\">";
- private static final String END_HTML = "</div></body>";
-
- private MessageWebView mWebView;
- private ConversationViewHeader mConversationHeaderView;
- private MessageHeaderView mMessageHeaderView;
- private MessageFooterView mMessageFooterView;
- private ConversationMessage mMessage;
- private MessageScrollView mScrollView;
-
- private int mSideMarginInWebPx;
-
- private ConversationViewProgressController mProgressController;
+ private SecureConversationViewController mViewController;
private class SecureConversationWebViewClient extends AbstractConversationWebViewClient {
- public SecureConversationWebViewClient(Context context, Account account) {
- super(context, account);
+ public SecureConversationWebViewClient(Account account) {
+ super(account);
}
@Override
@@ -79,7 +62,19 @@
onConversationSeen();
}
- mProgressController.dismissLoadingStatus();
+ mViewController.dismissLoadingStatus();
+
+ final Set<String> emailAddresses = Sets.newHashSet();
+ final List<Address> cacheCopy;
+ synchronized (mAddressCache) {
+ cacheCopy = ImmutableList.copyOf(mAddressCache.values());
+ }
+ for (Address addr : cacheCopy) {
+ emailAddresses.add(addr.getAddress());
+ }
+ final ContactLoaderCallbacks callbacks = getContactInfoSource();
+ callbacks.setSenders(emailAddresses);
+ getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
}
};
@@ -109,76 +104,93 @@
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
- mWebViewClient = new SecureConversationWebViewClient(getContext(), mAccount);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- mConversationHeaderView.setCallbacks(this, this);
- mConversationHeaderView.setFolders(mConversation);
- mConversationHeaderView.setSubject(mConversation.subject);
- mMessageHeaderView.initialize(mDateBuilder, this, mAddressCache);
- mMessageHeaderView.setExpandMode(MessageHeaderView.POPUP_MODE);
- mMessageHeaderView.setContactInfoSource(getContactInfoSource());
- mMessageHeaderView.setCallbacks(this);
- mMessageHeaderView.setExpandable(false);
- mMessageHeaderView.setVeiledMatcher(
- ((ControllableActivity) getActivity()).getAccountController()
- .getVeiledAddressMatcher());
- mMessageFooterView.initialize(getLoaderManager(), getFragmentManager());
- getLoaderManager().initLoader(MESSAGE_LOADER, null, getMessageLoaderCallbacks());
- mProgressController.showLoadingStatus(isUserVisible());
-
- final Resources r = getResources();
- mSideMarginInWebPx = (int) ((r.getDimensionPixelOffset(
- R.dimen.conversation_view_margin_side) + r.getDimensionPixelOffset(
- R.dimen.conversation_message_content_margin_side)) / r.getDisplayMetrics().density);
+ mWebViewClient = new SecureConversationWebViewClient(mAccount);
+ mViewController = new SecureConversationViewController(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.secure_conversation_view, container, false);
- mScrollView = (MessageScrollView) rootView.findViewById(R.id.scroll_view);
- mConversationHeaderView = (ConversationViewHeader) rootView.findViewById(R.id.conv_header);
- mMessageHeaderView = (MessageHeaderView) rootView.findViewById(R.id.message_header);
- mMessageFooterView = (MessageFooterView) rootView.findViewById(R.id.message_footer);
-
- mProgressController = new ConversationViewProgressController(this, getHandler());
- mProgressController.instantiateProgressIndicators(rootView);
- mWebView = (MessageWebView) rootView.findViewById(R.id.webview);
- mWebView.setWebViewClient(mWebViewClient);
- mWebView.setFocusable(false);
- final WebSettings settings = mWebView.getSettings();
-
- settings.setJavaScriptEnabled(false);
- settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
-
- ConversationViewUtils.setTextZoom(getResources(), settings);
-
- settings.setSupportZoom(true);
- settings.setBuiltInZoomControls(true);
- settings.setDisplayZoomControls(false);
-
- mScrollView.setInnerScrollableView(mWebView);
-
- return rootView;
+ return mViewController.onCreateView(inflater, container, savedInstanceState);
}
@Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mViewController.onActivityCreated(savedInstanceState);
+ }
+
+ // Start implementations of SecureConversationViewControllerCallbacks
+
+ @Override
+ public Fragment getFragment() {
+ return this;
+ }
+
+ @Override
+ public AbstractConversationWebViewClient getWebViewClient() {
+ return mWebViewClient;
+ }
+
+ @Override
+ public void setupConversationHeaderView(ConversationViewHeader headerView) {
+ headerView.setCallbacks(this, this);
+ headerView.setFolders(mConversation);
+ headerView.setSubject(mConversation.subject);
+ }
+
+ @Override
+ public boolean isViewVisibleToUser() {
+ return isUserVisible();
+ }
+
+ @Override
+ public ConversationAccountController getConversationAccountController() {
+ return this;
+ }
+
+ @Override
+ public Map<String, Address> getAddressCache() {
+ return mAddressCache;
+ }
+
+ @Override
+ public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView) {
+ messageHeaderView.setVeiledMatcher(
+ ((ControllableActivity) getActivity()).getAccountController()
+ .getVeiledAddressMatcher());
+ }
+
+ @Override
+ public void startMessageLoader() {
+ getLoaderManager().initLoader(MESSAGE_LOADER, null, getMessageLoaderCallbacks());
+ }
+
+ @Override
+ public String getBaseUri() {
+ return mBaseUri;
+ }
+
+ @Override
+ public boolean isViewOnlyMode() {
+ return false;
+ }
+
+ // End implementations of SecureConversationViewControllerCallbacks
+
+ @Override
protected void markUnread() {
super.markUnread();
// Ignore unsafe calls made after a fragment is detached from an activity
final ControllableActivity activity = (ControllableActivity) getActivity();
- if (activity == null || mConversation == null || mMessage == null) {
+ final ConversationMessage message = mViewController.getMessage();
+ if (activity == null || mConversation == null || message == null) {
LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s",
mConversation != null ? mConversation.id : 0);
return;
}
final HashSet<Uri> uris = new HashSet<Uri>();
- uris.add(mMessage.uri);
+ uris.add(message.uri);
activity.getConversationUpdater().markConversationMessagesUnread(mConversation, uris,
mViewState.getConversationInfo());
}
@@ -204,41 +216,6 @@
}
@Override
- public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight) {
- // Do nothing.
- }
-
- @Override
- public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeight) {
- // Do nothing.
- }
-
- @Override
- public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightbefore) {
- // Do nothing.
- }
-
- @Override
- public void showExternalResources(final Message msg) {
- mWebView.getSettings().setBlockNetworkImage(false);
- }
-
- @Override
- public void showExternalResources(final String rawSenderAddress) {
- mWebView.getSettings().setBlockNetworkImage(false);
- }
-
- @Override
- public boolean supportsMessageTransforms() {
- return false;
- }
-
- @Override
- public String getMessageTransforms(final Message msg) {
- return null;
- }
-
- @Override
protected void onMessageCursorLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader,
MessageCursor newCursor, MessageCursor oldCursor) {
// ignore cursors that are still loading results
@@ -250,48 +227,25 @@
// Activity is finishing, just bail.
return;
}
- renderMessageBodies(newCursor);
- }
-
- /**
- * Populate the adapter with overlay views (message headers, super-collapsed
- * blocks, a conversation header), and return an HTML document with spacer
- * divs inserted for all overlays.
- */
- private void renderMessageBodies(MessageCursor messageCursor) {
- if (!messageCursor.moveToFirst()) {
+ if (!newCursor.moveToFirst()) {
LogUtils.e(LOG_TAG, "unable to open message cursor");
return;
}
- mMessage = messageCursor.getMessage();
- mWebView.getSettings().setBlockNetworkImage(!mMessage.alwaysShowImages);
- // Add formatting to message body
- // At this point, only adds margins.
- StringBuilder dataBuilder = new StringBuilder(
- String.format(BEGIN_HTML, mSideMarginInWebPx));
- dataBuilder.append(mMessage.getBodyAsHtml());
- dataBuilder.append(END_HTML);
-
- mWebView.loadDataWithBaseURL(mBaseUri, dataBuilder.toString(), "text/html", "utf-8", null);
- final ConversationViewAdapter adapter = new ConversationViewAdapter(mActivity, null, null,
- null, null, null, null, null, null);
- final MessageHeaderItem item = adapter.newMessageHeaderItem(mMessage, true,
- mMessage.alwaysShowImages);
- mMessageHeaderView.bind(item, false);
- if (mMessage.hasAttachments) {
- mMessageFooterView.setVisibility(View.VISIBLE);
- mMessageFooterView.bind(item, false);
- }
+ mViewController.renderMessage(newCursor.getMessage());
}
@Override
public void onConversationUpdated(Conversation conv) {
- final ConversationViewHeader headerView = mConversationHeaderView;
+ final ConversationViewHeader headerView = mViewController.getConversationHeaderView();
if (headerView != null) {
headerView.onConversationUpdated(conv);
headerView.setSubject(conv.subject);
}
}
+ // Need this stub here
+ public boolean supportsMessageTransforms() {
+ return false;
+ }
}
diff --git a/src/com/android/mail/utils/MimeType.java b/src/com/android/mail/utils/MimeType.java
index b17cdcb..ab2c6e4 100644
--- a/src/com/android/mail/utils/MimeType.java
+++ b/src/com/android/mail/utils/MimeType.java
@@ -41,7 +41,7 @@
static final String GENERIC_MIMETYPE = "application/octet-stream";
@VisibleForTesting
- public static final Set<String> EML_ATTACHMENT_CONTENT_TYPES = ImmutableSet.of(
+ private static final Set<String> EML_ATTACHMENT_CONTENT_TYPES = ImmutableSet.of(
"message/rfc822", "application/eml");
public static final String EML_ATTACHMENT_CONTENT_TYPE = "message/rfc822";
private static final String NULL_ATTACHMENT_CONTENT_TYPE = "null";
@@ -164,4 +164,14 @@
}
}
}
+
+ /**
+ * Checks the supplied mime type to determine if it is a valid eml file.
+ * Valid mime types are "message/rfc822" and "application/eml".
+ * @param mimeType the mime type to check
+ * @return {@code true} if the mime type is one of the valid mime types.
+ */
+ public static boolean isEmlMimeType(String mimeType) {
+ return EML_ATTACHMENT_CONTENT_TYPES.contains(mimeType);
+ }
}
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 2327e1f..e5b9e10 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -1287,4 +1287,64 @@
return uri.buildUpon().appendQueryParameter(APP_VERSION_QUERY_PARAMETER,
Integer.toString(appVersion)).build();
}
+
+ /**
+ * Gets the specified {@link Folder} object.
+ *
+ * @param folderUri The {@link Uri} for the folder
+ * @param allowHidden <code>true</code> to allow a hidden folder to be returned,
+ * <code>false</code> to return <code>null</code> instead
+ * @return the specified {@link Folder} object, or <code>null</code>
+ */
+ public static Folder getFolder(final Context context, final Uri folderUri,
+ final boolean allowHidden) {
+ final Uri uri = folderUri
+ .buildUpon()
+ .appendQueryParameter(UIProvider.ALLOW_HIDDEN_FOLDERS_QUERY_PARAM,
+ Boolean.toString(allowHidden))
+ .build();
+
+ final Cursor cursor = context.getContentResolver().query(uri,
+ UIProvider.FOLDERS_PROJECTION, null, null, null);
+
+ if (cursor == null) {
+ return null;
+ }
+
+ try {
+ if (cursor.moveToFirst()) {
+ return new Folder(cursor);
+ } else {
+ return null;
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Begins systrace tracing for a given tag. No-op on unsupported platform versions.
+ *
+ * @param tag systrace tag to use
+ *
+ * @see android.os.Trace#beginSection(String)
+ */
+ public static void traceBeginSection(String tag) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ android.os.Trace.beginSection(tag);
+ }
+ }
+
+ /**
+ * Ends systrace tracing for the most recently begun section. No-op on unsupported platform
+ * versions.
+ *
+ * @see android.os.Trace#endSection()
+ */
+ public static void traceEndSection() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ android.os.Trace.endSection();
+ }
+ }
+
}