Merge "Add from accounts dropdown."
diff --git a/res/layout/compose_from.xml b/res/layout/compose_from.xml
index 154ff31..92ee5cb 100644
--- a/res/layout/compose_from.xml
+++ b/res/layout/compose_from.xml
@@ -24,7 +24,8 @@
<LinearLayout android:id="@+id/spinner_from_content"
style="@style/RecipientComposeFieldLayout"
android:background="@android:color/transparent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:visibility="gone">
<Spinner android:id="@+id/from_picker"
android:layout_width="match_parent"
diff --git a/res/layout/from_dropdown_item.xml b/res/layout/from_dropdown_item.xml
new file mode 100644
index 0000000..934f22d
--- /dev/null
+++ b/res/layout/from_dropdown_item.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2011 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent">
+
+ <TextView
+ android:id="@+id/spinner_account_name"
+ android:singleLine="true"
+ android:layout_width="wrap_content"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_weight="1"
+ android:ellipsize="end"
+ android:paddingLeft="8dip"
+ android:gravity="center_vertical" />
+
+</LinearLayout>
diff --git a/res/layout/from_item.xml b/res/layout/from_item.xml
new file mode 100644
index 0000000..56662da
--- /dev/null
+++ b/res/layout/from_item.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2011 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/spinner_account_name"
+ android:layout_width="wrap_content"
+ android:layout_height="48dip"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:paddingLeft="8dip"
+ android:gravity="center_vertical"/>
\ No newline at end of file
diff --git a/src/com/android/email/compose/ComposeActivity.java b/src/com/android/email/compose/ComposeActivity.java
index f3ea16d..37ac859 100644
--- a/src/com/android/email/compose/ComposeActivity.java
+++ b/src/com/android/email/compose/ComposeActivity.java
@@ -41,9 +41,12 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
+import android.widget.Spinner;
import android.widget.TextView;
import com.android.common.Rfc822Validator;
@@ -52,6 +55,7 @@
import com.android.email.providers.Attachment;
import com.android.email.providers.protos.mock.MockAttachment;
import com.android.email.R;
+import com.android.email.utils.AccountUtils;
import com.android.email.utils.MimeType;
import com.android.email.utils.Utils;
import com.android.ex.chips.RecipientEditTextView;
@@ -61,12 +65,13 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
public class ComposeActivity extends Activity implements OnClickListener, OnNavigationListener,
- RespondInlineListener {
+ RespondInlineListener, OnItemSelectedListener {
// Identifiers for which type of composition this is
static final int COMPOSE = -1; // also used for editing a draft
static final int REPLY = 0;
@@ -122,6 +127,14 @@
private boolean mAttachmentsChanged;
private QuotedTextView mQuotedTextView;
private TextView mBodyText;
+ private View mFromStatic;
+ private View mFromSpinner;
+ private Spinner mFrom;
+ private List<String[]> mReplyFromAccounts;
+ private boolean mAccountSpinnerReady;
+ private String[] mCurrentReplyFromAccount;
+ private boolean mMessageIsForwardOrReply;
+ private List<String> mAccounts;
/**
* Can be called from a non-UI thread.
@@ -174,13 +187,123 @@
findViews();
Intent intent = getIntent();
int action = intent.getIntExtra(EXTRA_ACTION, COMPOSE);
- initActionBar(action);
if (action == REPLY || action == REPLY_ALL || action == FORWARD) {
mRefMessageUri = Uri.parse(intent.getStringExtra(EXTRA_IN_REFERENCE_TO_MESSAGE_URI));
initFromRefMessage(action, mAccount);
} else {
setQuotedTextVisibility(false);
}
+ initActionBar(action);
+ asyncInitFromSpinner();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // Update the from spinner as other accounts
+ // may now be available.
+ asyncInitFromSpinner();
+ }
+
+ private void asyncInitFromSpinner() {
+ Account[] result = AccountUtils.getSyncingAccounts(this, null, null, null);
+ mAccounts = AccountUtils
+ .mergeAccountLists(mAccounts, result, true /* prioritizeAccountList */);
+ createReplyFromCache();
+ initFromSpinner();
+ }
+
+ /**
+ * Create a cache of all accounts a user could send mail from
+ */
+ private void createReplyFromCache() {
+ // Check for replyFroms.
+ List<String> accounts = null;
+ mReplyFromAccounts = new ArrayList<String[]>();
+
+ if (mMessageIsForwardOrReply) {
+ accounts = Collections.singletonList(mAccount);
+ } else {
+ accounts = mAccounts;
+ }
+ for (String account : accounts) {
+ // First add the account. First position is account, second
+ // is display of account, 3rd position is the REAL account this
+ // is being sent from / synced to.
+ mReplyFromAccounts.add(new String[] {
+ account, account, account, "false"
+ });
+ }
+ }
+
+ private void initFromSpinner() {
+ // If there are not yet any accounts in the cached synced accounts
+ // because this is the first time Gmail was opened, and it was opened directly
+ // to the compose activity,don't bother populating the reply from spinner yet.
+ if (mReplyFromAccounts == null || mReplyFromAccounts.size() == 0) {
+ mAccountSpinnerReady = false;
+ return;
+ }
+ FromAddressSpinnerAdapter adapter = new FromAddressSpinnerAdapter(this);
+ int currentAccountIndex = 0;
+ String replyFromAccount = mAccount;
+
+ boolean checkRealAccount = mRecipient == null || mAccount.equals(mRecipient);
+
+ currentAccountIndex = addAccountsToAdapter(adapter, checkRealAccount, replyFromAccount);
+
+ mFrom.setAdapter(adapter);
+ mFrom.setSelection(currentAccountIndex, false);
+ mFrom.setOnItemSelectedListener(this);
+ mCurrentReplyFromAccount = mReplyFromAccounts.get(currentAccountIndex);
+
+ hideOrShowFromSpinner();
+ mAccountSpinnerReady = true;
+ adapter.setSpinner(mFrom);
+ }
+
+ private void hideOrShowFromSpinner() {
+ // Determine whether the from account spinner or the static
+ // from text should be show
+ // When the spinner is shown, the static from text
+ // is hidden
+ showFromSpinner(mFrom.getCount() > 1);
+ }
+
+ private int addAccountsToAdapter(FromAddressSpinnerAdapter adapter, boolean checkRealAccount,
+ String replyFromAccount) {
+ int currentIndex = 0;
+ int currentAccountIndex = 0;
+ // Get the position of the current account
+ for (String[] account : mReplyFromAccounts) {
+ // Add the account to the Adapter
+ // The reason that we are not adding the Account array, but adding
+ // the names of each account, is because Account returns a string
+ // that we don't want to display on toString()
+ adapter.add(account);
+ // Compare to the account address, not the real account being
+ // sent from.
+ if (checkRealAccount) {
+ // Need to check the real account and the account address
+ // so that we can send from the correct address on the
+ // correct account when the same address may exist across
+ // multiple accounts.
+ if (account[FromAddressSpinnerAdapter.REAL_ACCOUNT].equals(mAccount)
+ && account[FromAddressSpinnerAdapter.ACCOUNT_ADDRESS]
+ .equals(replyFromAccount)) {
+ currentAccountIndex = currentIndex;
+ }
+ } else {
+ // Just need to check the account address.
+ if (replyFromAccount.equals(
+ account[FromAddressSpinnerAdapter.ACCOUNT_ADDRESS])) {
+ currentAccountIndex = currentIndex;
+ }
+ }
+
+ currentIndex++;
+ }
+ return currentAccountIndex;
}
private void findViews() {
@@ -197,6 +320,23 @@
mQuotedTextView = (QuotedTextView) findViewById(R.id.quoted_text_view);
mQuotedTextView.setRespondInlineListener(this);
mBodyText = (TextView) findViewById(R.id.body);
+ mFromStatic = findViewById(R.id.static_from_content);
+ mFromSpinner = findViewById(R.id.spinner_from_content);
+ mFrom = (Spinner) findViewById(R.id.from_picker);
+ }
+
+ /**
+ * Show the static from text view or the spinner
+ * @param showSpinner Whether the spinner should be shown
+ */
+ private void showFromSpinner(boolean showSpinner) {
+ // show/hide the static text
+ mFromStatic.setVisibility(
+ showSpinner ? View.GONE : View.VISIBLE);
+
+ // show/hide the spinner
+ mFromSpinner.setVisibility(
+ showSpinner ? View.VISIBLE : View.GONE);
}
private void setQuotedTextVisibility(boolean show) {
@@ -478,4 +618,14 @@
public void setBody(CharSequence text, boolean withSignature) {
mBodyText.setText(text);
}
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ // TODO
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing.
+ }
}
\ No newline at end of file
diff --git a/src/com/android/email/compose/FromAddressSpinnerAdapter.java b/src/com/android/email/compose/FromAddressSpinnerAdapter.java
new file mode 100644
index 0000000..d9c0872
--- /dev/null
+++ b/src/com/android/email/compose/FromAddressSpinnerAdapter.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2011, Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.email.compose;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.email.R;
+
+/**
+ * FromAddressSpinnerAdapter returns the correct spinner adapter for reply from
+ * addresses based on device size.
+ *
+ * @author mindyp@google.com
+ */
+public class FromAddressSpinnerAdapter extends ArrayAdapter<String[]> {
+ public static int REAL_ACCOUNT = 2;
+
+ public static int ACCOUNT_DISPLAY = 0;
+
+ public static int ACCOUNT_ADDRESS = 1;
+
+ private LayoutInflater mInflater;
+
+ private Spinner mSpinner;
+
+ public FromAddressSpinnerAdapter(Context context) {
+ super(context, R.layout.from_item, R.id.spinner_account_name);
+ }
+
+ protected LayoutInflater getInflater() {
+ if (mInflater == null) {
+ mInflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+ return mInflater;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ String[] fromItem = getItem(position);
+ View fromEntry = getInflater().inflate(R.layout.from_item, null);
+ ((TextView) fromEntry.findViewById(R.id.spinner_account_name))
+ .setText(fromItem[ACCOUNT_ADDRESS]);
+ return fromEntry;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ String[] fromItem = getItem(position);
+ View fromEntry = getInflater().inflate(R.layout.from_dropdown_item, null);
+ TextView acctName = ((TextView) fromEntry.
+ findViewById(R.id.spinner_account_name));
+ acctName.setText(fromItem[ACCOUNT_DISPLAY]);
+ return fromEntry;
+ }
+
+ /**
+ * Set the spinner this adapter for which this spinner is being used.
+ *
+ * @param spinner Spinner widget.
+ */
+ public void setSpinner(Spinner spinner) {
+ mSpinner = spinner;
+ }
+
+ /**
+ * Get the spinner associated with this adapter.
+ *
+ * @return Spinner widget.
+ */
+ public Spinner getSpinner() {
+ return mSpinner;
+ }
+
+ public int getSelectedItemPosition() {
+ return mSpinner != null ? mSpinner.getSelectedItemPosition() : -1;
+ }
+}
diff --git a/src/com/android/email/utils/AccountUtils.java b/src/com/android/email/utils/AccountUtils.java
new file mode 100644
index 0000000..998185d
--- /dev/null
+++ b/src/com/android/email/utils/AccountUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.email.utils;
+
+import com.android.email.providers.UIProvider;
+import com.android.email.providers.protos.mock.MockUiProvider;
+
+import android.accounts.Account;
+import android.accounts.AccountManagerCallback;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AccountUtils {
+ /**
+ * Merge two lists of accounts into one list of accounts without duplicates.
+ *
+ * @param existingList List of accounts.
+ * @param accounts Accounts to merge in.
+ * @param prioritizeAccountList Boolean indicating whether this method
+ * should prioritize the list of Account objects when merging the
+ * lists
+ * @return Merged list of accounts.
+ */
+ public static List<String> mergeAccountLists(List<String> existingList, Account[] accounts,
+ boolean prioritizeAccountList) {
+
+ List<String> newAccountList = new ArrayList<String>();
+ // Make sure the accounts are actually synchronized
+ // (we won't be able to save/send for accounts that
+ // have never been synchronized)
+ for (int i = 0; i < accounts.length; i++) {
+ final String accountName = accounts[i].name;
+ // If the account is in the cached list or the caller requested
+ // that we prioritize the list of Account objects, put it in the new list
+ if (prioritizeAccountList
+ || (existingList != null && existingList.contains(accountName))) {
+ newAccountList.add(accountName);
+ }
+ }
+ return newAccountList;
+ }
+
+ /**
+ * In the future, this will get syncing accounts from the account manager.
+ * Currently, it just gets all accounts from the UIProvider.
+ * @param context
+ * @param callback
+ * @param type
+ * @param features
+ * @return
+ */
+ public static Account[] getSyncingAccounts(Context context,
+ AccountManagerCallback<Account[]> callback, String type, String[] features) {
+ // TODO: use account manager.
+ // AccountManager.get(context).getAccountsByTypeAndFeatures(type, features, callback, null);
+ ContentResolver resolver = context.getContentResolver();
+ Cursor accountsCursor = resolver.query(MockUiProvider.getAccountsUri(),
+ UIProvider.ACCOUNTS_PROJECTION, null, null, null);
+ ArrayList<Account> accounts = new ArrayList<Account>();
+ if (accountsCursor != null) {
+ while (accountsCursor.moveToNext()) {
+ accounts.add(new Account(accountsCursor.getString(UIProvider.ACCOUNT_NAME_COLUMN),
+ "unknown"));
+ }
+ }
+ return accounts.toArray(new Account[accounts.size()]);
+ }
+}
diff --git a/src/com/android/email/utils/AttachmentUtils.java b/src/com/android/email/utils/AttachmentUtils.java
index 2e9a03a..d115740 100644
--- a/src/com/android/email/utils/AttachmentUtils.java
+++ b/src/com/android/email/utils/AttachmentUtils.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.email.utils;
import android.content.Context;