Merge "PhotoViewer handles single URIs more robustly." into jb-ub-mail
diff --git a/res/values/constants.xml b/res/values/constants.xml
index ccf4f44..0b255ed 100644
--- a/res/values/constants.xml
+++ b/res/values/constants.xml
@@ -66,4 +66,7 @@
 
     <!-- Number of decoded contact photo bitmaps retained in an LRU cache -->
     <integer name="config_image_cache_max_bitmaps">24</integer>
+
+    <!-- Whether to display folder colors in the widget -->
+    <bool name="display_folder_colors_in_widget">false</bool>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 00223b5..484d6a2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -693,4 +693,9 @@
 
     <!-- Camera format string for new image files. Passed to java.text.SimpleDateFormat. -->
     <string name="image_file_name_format" translatable="false">"'IMG'_yyyyMMdd_HHmmss"</string>
+
+    <!--  The move message / change labels action can't be taken because the selected messages
+        come from different accounts -->
+    <string name="cant_move_or_change_labels">Can\'t move because selection contains multiple
+        accounts.</string>
 </resources>
diff --git a/src/com/android/mail/browse/SelectedConversationsActionMenu.java b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
index b3a9488..13fd7cb 100644
--- a/src/com/android/mail/browse/SelectedConversationsActionMenu.java
+++ b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
@@ -20,24 +20,27 @@
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.net.Uri;
 import android.view.ActionMode;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.widget.Toast;
 
 import com.android.mail.R;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
+import com.android.mail.providers.MailAppProvider;
 import com.android.mail.providers.Settings;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.providers.UIProvider.ConversationColumns;
 import com.android.mail.providers.UIProvider.FolderCapabilities;
 import com.android.mail.ui.AbstractActivityController;
-import com.android.mail.ui.DestructiveAction;
 import com.android.mail.ui.AnimatedAdapter;
 import com.android.mail.ui.ConversationSelectionSet;
 import com.android.mail.ui.ConversationSetObserver;
+import com.android.mail.ui.DestructiveAction;
 import com.android.mail.ui.FoldersSelectionDialog;
 import com.android.mail.ui.RestrictedActivity;
 import com.android.mail.ui.SwipeableListView;
@@ -158,8 +161,31 @@
                 }
                 break;
             case R.id.change_folder:
-                new FoldersSelectionDialog(mContext, mAccount, mController, mSelectionSet.values(),
-                        true).show();
+                boolean cantMove = false;
+                Account acct = mAccount;
+                // Special handling for virtual folders
+                if (mFolder.supportsCapability(FolderCapabilities.IS_VIRTUAL)) {
+                    Uri accountUri = null;
+                    for (Conversation conv: mSelectionSet.values()) {
+                        if (accountUri == null) {
+                            accountUri = conv.accountUri;
+                        } else if (!accountUri.equals(conv.accountUri)) {
+                            // Tell the user why we can't do this
+                            Toast.makeText(mContext, R.string.cant_move_or_change_labels,
+                                    Toast.LENGTH_LONG).show();
+                            cantMove = true;
+                            break;
+                        }
+                    }
+                    if (!cantMove) {
+                        // Get the actual account here, so that we display its folders in the dialog
+                        acct = MailAppProvider.getAccountFromAccountUri(accountUri);
+                    }
+                }
+                if (!cantMove) {
+                    new FoldersSelectionDialog(mContext, acct, mController,
+                            mSelectionSet.values(), true).show();
+                }
                 break;
             case R.id.mark_important:
                 markConversationsImportant(true);
diff --git a/src/com/android/mail/providers/Conversation.java b/src/com/android/mail/providers/Conversation.java
index cba2ac7..1bb7d1c 100644
--- a/src/com/android/mail/providers/Conversation.java
+++ b/src/com/android/mail/providers/Conversation.java
@@ -16,14 +16,14 @@
 
 package com.android.mail.providers;
 
-import com.google.common.collect.ImmutableList;
-
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.google.common.collect.ImmutableList;
+
 import java.util.Collection;
 import java.util.Collections;
 
@@ -51,6 +51,7 @@
     public boolean spam;
     public boolean muted;
     public int color;
+    public Uri accountUri;
 
     // Used within the UI to indicate the adapter position of this conversation
     public transient int position;
@@ -96,6 +97,7 @@
         dest.writeInt(spam ? 1 : 0);
         dest.writeInt(muted ? 1 : 0);
         dest.writeInt(color);
+        dest.writeParcelable(accountUri, 0);
     }
 
     private Conversation(Parcel in) {
@@ -120,6 +122,7 @@
         spam = in.readInt() != 0;
         muted = in.readInt() != 0;
         color = in.readInt();
+        accountUri = in.readParcelable(null);
         position = NO_POSITION;
         localDeleteOnUpdate = false;
     }
@@ -174,6 +177,8 @@
             spam = cursor.getInt(UIProvider.CONVERSATION_IS_SPAM_COLUMN) != 0;
             muted = cursor.getInt(UIProvider.CONVERSATION_MUTED_COLUMN) != 0;
             color = cursor.getInt(UIProvider.CONVERSATION_COLOR_COLUMN);
+            String account = cursor.getString(UIProvider.CONVERSATION_ACCOUNT_URI_COLUMN);
+            accountUri = !TextUtils.isEmpty(account) ? Uri.parse(account) : null;
             position = NO_POSITION;
             localDeleteOnUpdate = false;
         }
@@ -186,7 +191,7 @@
             String snippet, boolean hasAttachment, Uri messageListUri, String senders,
             int numMessages, int numDrafts, int sendingState, int priority, boolean read,
             boolean starred, String folderList, String rawFolders, int convFlags,
-            int personalLevel, boolean spam, boolean muted) {
+            int personalLevel, boolean spam, boolean muted, Uri accountUri) {
 
         final Conversation conversation = new Conversation();
 
@@ -211,6 +216,7 @@
         conversation.spam = spam;
         conversation.muted = muted;
         conversation.color = 0;
+        conversation.accountUri = accountUri;
         return conversation;
     }
 
diff --git a/src/com/android/mail/providers/MailAppProvider.java b/src/com/android/mail/providers/MailAppProvider.java
index f2d022f..d2a079b 100644
--- a/src/com/android/mail/providers/MailAppProvider.java
+++ b/src/com/android/mail/providers/MailAppProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.mail.providers;
 
+import android.app.Activity;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -459,6 +460,19 @@
         return mSharedPrefs;
     }
 
+    static public Account getAccountFromAccountUri(Uri accountUri) {
+        MailAppProvider provider = getInstance();
+        if (provider != null && provider.mAccountsFullyLoaded) {
+            synchronized(provider.mAccountCache) {
+                AccountCacheEntry entry = provider.mAccountCache.get(accountUri);
+                if (entry != null) {
+                    return entry.mAccount;
+                }
+            }
+        }
+        return null;
+    }
+
     @Override
     public void onLoadComplete(Loader<Cursor> loader, Cursor data) {
         if (data == null) {
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index 228530e..d5ef631 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -572,6 +572,12 @@
          * Deletions in this folder can't be undone (could include archive if desirable)
          */
         public static final int DELETE_ACTION_FINAL = 0x0200;
+        /**
+         * This folder is virtual, i.e. contains conversations potentially pulled from other
+         * folders, potentially even from different accounts.  Examples might be a "starred"
+         * folder, or an "unread" folder (per account or provider-wide)
+         */
+        public static final int IS_VIRTUAL = 0x400;
     }
 
     public static final class FolderColumns {
@@ -679,7 +685,8 @@
         ConversationColumns.PERSONAL_LEVEL,
         ConversationColumns.SPAM,
         ConversationColumns.MUTED,
-        ConversationColumns.COLOR
+        ConversationColumns.COLOR,
+        ConversationColumns.ACCOUNT_URI
     };
 
     // These column indexes only work when the caller uses the
@@ -705,6 +712,7 @@
     public static final int CONVERSATION_IS_SPAM_COLUMN = 18;
     public static final int CONVERSATION_MUTED_COLUMN = 19;
     public static final int CONVERSATION_COLOR_COLUMN = 20;
+    public static final int CONVERSATION_ACCOUNT_URI_COLUMN = 21;
 
     public static final class ConversationSendingState {
         public static final int OTHER = 0;
@@ -833,6 +841,11 @@
          */
         public static final String COLOR = "color";
 
+        /**
+         * This String column contains the Uri for this conversation's account
+         */
+        public static final String ACCOUNT_URI = "accountUri";
+
         private ConversationColumns() {
         }
     }
diff --git a/src/com/android/mail/widget/WidgetConversationViewBuilder.java b/src/com/android/mail/widget/WidgetConversationViewBuilder.java
index 59acd6e..3fe6d23 100644
--- a/src/com/android/mail/widget/WidgetConversationViewBuilder.java
+++ b/src/com/android/mail/widget/WidgetConversationViewBuilder.java
@@ -18,6 +18,11 @@
 
 import com.android.mail.R;
 import com.android.mail.providers.Account;
+import com.android.mail.providers.Folder;
+import com.android.mail.ui.FolderDisplayer;
+import com.android.mail.widget.WidgetConversationViewBuilder.WidgetFolderDisplayer;
+
+import java.util.Map;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -51,6 +56,64 @@
     private static Bitmap ATTACHMENT;
 
     private final Context mContext;
+    private WidgetFolderDisplayer mFolderDisplayer;
+
+    /**
+     * Label Displayer for Widget
+     */
+    protected static class WidgetFolderDisplayer extends FolderDisplayer {
+        public WidgetFolderDisplayer(Context context) {
+            super(context);
+        }
+
+        // Maximum number of folders we want to display
+        private static final int MAX_DISPLAYED_FOLDERS_COUNT = 3;
+
+        /*
+         * Load Conversation Labels
+         */
+        @Override
+        public void loadConversationFolders(String rawFolders, Folder ignoreFolder) {
+            super.loadConversationFolders(rawFolders, ignoreFolder);
+        }
+
+        private int getFolderViewId(int position) {
+            switch (position) {
+                case 0:
+                    return R.id.widget_folder_0;
+                case 1:
+                    return R.id.widget_folder_1;
+                case 2:
+                    return R.id.widget_folder_2;
+            }
+            return 0;
+        }
+
+        /**
+         * Display folders
+         */
+        public void displayFolders(RemoteViews remoteViews) {
+            int displayedFolder = 0;
+            for (Folder folderValues : mFoldersSortedSet) {
+                int viewId = getFolderViewId(displayedFolder);
+                if (viewId == 0) {
+                    continue;
+                }
+                remoteViews.setViewVisibility(viewId, View.VISIBLE);
+                int color[] = new int[] {folderValues.getBackgroundColor(mDefaultBgColor)};
+                Bitmap bitmap = Bitmap.createBitmap(color, 1, 1, Bitmap.Config.RGB_565);
+                remoteViews.setImageViewBitmap(viewId, bitmap);
+
+                if (++displayedFolder == MAX_DISPLAYED_FOLDERS_COUNT) {
+                    break;
+                }
+            }
+
+            for (int i = displayedFolder; i < MAX_DISPLAYED_FOLDERS_COUNT; i++) {
+                remoteViews.setViewVisibility(getFolderViewId(i), View.GONE);
+            }
+        }
+    }
 
     /*
      * Get font sizes and bitmaps from Resources
@@ -94,9 +157,9 @@
     /*
      * Return the full View
      */
-    public RemoteViews getStyledView(
-            CharSequence senders, CharSequence status, CharSequence date, CharSequence subject,
-            CharSequence snippet, String folders, boolean hasAttachments, boolean read) {
+    public RemoteViews getStyledView(CharSequence senders, CharSequence status, CharSequence date,
+            CharSequence subject, CharSequence snippet, String folders, boolean hasAttachments,
+            boolean read, Folder currentFolder) {
 
         final boolean isUnread = !read;
 
@@ -159,6 +222,11 @@
             remoteViews.setViewVisibility(R.id.widget_unread_background, View.GONE);
             remoteViews.setViewVisibility(R.id.widget_read_background, View.VISIBLE);
         }
+        if (mContext.getResources().getBoolean(R.bool.display_folder_colors_in_widget)) {
+            mFolderDisplayer = new WidgetFolderDisplayer(mContext);
+            mFolderDisplayer.loadConversationFolders(folders, currentFolder);
+            mFolderDisplayer.displayFolders(remoteViews);
+        }
 
         return remoteViews;
     }
diff --git a/src/com/android/mail/widget/WidgetService.java b/src/com/android/mail/widget/WidgetService.java
index bd50886..23dc6c7 100644
--- a/src/com/android/mail/widget/WidgetService.java
+++ b/src/com/android/mail/widget/WidgetService.java
@@ -64,7 +64,7 @@
     /**
      * Remote Views Factory for Mail Widget.
      */
-    private static class MailFactory
+    protected static class MailFactory
             implements RemoteViewsService.RemoteViewsFactory, OnLoadCompleteListener<Cursor> {
         private static final int MAX_CONVERSATIONS_COUNT = 25;
         private static final int MAX_SENDERS_LENGTH = 25;
@@ -91,7 +91,8 @@
                     AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
             mAccount = Account.newinstance(intent.getStringExtra(WidgetProvider.EXTRA_ACCOUNT));
             mFolder = new Folder(intent.getStringExtra(WidgetProvider.EXTRA_FOLDER));
-            mWidgetConversationViewBuilder = new WidgetConversationViewBuilder(mContext, mAccount);
+            mWidgetConversationViewBuilder = new WidgetConversationViewBuilder(context,
+                    mAccount);
             mResolver = context.getContentResolver();
             mService = service;
         }
@@ -221,8 +222,8 @@
                 // Load up our remote view.
                 RemoteViews remoteViews = mWidgetConversationViewBuilder.getStyledView(
                         senderBuilder, statusBuilder, date, filterTag(conversation.subject),
-                        conversation.snippet, conversation.folderList, conversation.hasAttachments,
-                        conversation.read);
+                        conversation.snippet, conversation.rawFolders, conversation.hasAttachments,
+                        conversation.read, mFolder);
 
                 // On click intent.
                 remoteViews.setOnClickFillInIntent(R.id.widget_conversation,