Add a second fragment to select the mailbox

After choosing the account, we need to select a mailbox for the shortcut.
In order to replace the fragment, we cannot hardcode it in the XML. Instead,
we need to dynamically create it.

Also, restructure the fragment class and rely on the class to "do the right
thing"

Change-Id: I752ad5bbdf2484332ec2b73852cae74a5d2092fa
This commit is contained in:
Todd Kennedy 2011-05-24 09:20:14 -07:00
parent e4a4eb66b1
commit 5675ea88d3
10 changed files with 310 additions and 177 deletions

View File

@ -179,9 +179,9 @@
>
</activity>
<!-- Don't need to set the title; it will be set programatically -->
<activity
android:name=".activity.AccountShortcutPicker"
android:label="@string/account_shortcut_picker_title"
android:name=".activity.ShortcutPicker"
android:enabled="false"
android:theme="@android:style/Theme.Holo.DialogWhenLarge"
>

View File

@ -35,6 +35,12 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par
public static final String TABLE_NAME = "HostAuth";
@SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
// TODO the three following constants duplicate constants in Store.java; remove those and
// just reference these.
public static final String SCHEME_IMAP = "imap";
public static final String SCHEME_POP3 = "pop3";
public static final String SCHEME_EAS = "eas";
public static final String SCHEME_SMTP = "smtp";
public static final int PORT_UNKNOWN = -1;
@ -267,18 +273,23 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par
// block cannot easily be moved because we get process URIs from other sources
// (e.g. for tests, provider templates and account restore) that may or may not
// have a port specified.
if ("pop3".equals(mProtocol)) {
if (SCHEME_POP3.equals(mProtocol)) {
mPort = useSSL ? 995 : 110;
} else if ("imap".equals(mProtocol)) {
} else if (SCHEME_IMAP.equals(mProtocol)) {
mPort = useSSL ? 993 : 143;
} else if ("eas".equals(mProtocol)) {
} else if (SCHEME_EAS.equals(mProtocol)) {
mPort = useSSL ? 443 : 80;
} else if ("smtp".equals(mProtocol)) {
} else if (SCHEME_SMTP.equals(mProtocol)) {
mPort = useSSL ? 465 : 587;
}
}
}
/** Returns {@code true} if this is an EAS connection; otherwise, {@code false}. */
public boolean isEasConnection() {
return SCHEME_EAS.equals(mProtocol);
}
/**
* Supports Parcelable
*/
@ -356,4 +367,4 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par
&& Utility.areStringsEqual(mPassword, that.mPassword)
&& Utility.areStringsEqual(mDomain, that.mDomain);
}
}
}

View File

@ -20,7 +20,7 @@
android:layout_height="match_parent"
android:orientation="vertical"
>
<fragment android:name="com.android.email.activity.AccountShortcutPickerFragment"
<FrameLayout
android:id="@+id/shortcut_list"
android:layout_width="match_parent"
android:layout_height="0dip"

View File

@ -940,8 +940,10 @@ save attachment.</string>
<!-- Strings used for account shortcut picker -->
<!-- String displayed in launcher [CHAR_LIMIT=10] -->
<string name="account_shortcut_picker_name">Email account</string>
<!-- Title of activity/dialog containing shortcut picker (list of accounts) [CHAR_LIMIT=20] -->
<!-- Title of the account list for the shortcut picker [CHAR_LIMIT=20] -->
<string name="account_shortcut_picker_title">Select an account</string>
<!-- Title of mailbox list for the shortcut picker [CHAR_LIMIT=20] -->
<string name="mailbox_shortcut_picker_title">Select a mailbox</string>
<!-- Toast shown when the selected account no longer exists. This is used when, for example,
a shotcut or a widget is stale and points at a deleted account.

View File

@ -16,7 +16,7 @@
package com.android.email;
import com.android.email.activity.AccountShortcutPicker;
import com.android.email.activity.ShortcutPicker;
import com.android.email.activity.MessageCompose;
import com.android.email.service.AttachmentDownloadService;
import com.android.email.service.MailService;
@ -142,7 +142,7 @@ public class Email extends Application {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(
new ComponentName(context, AccountShortcutPicker.class),
new ComponentName(context, ShortcutPicker.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);

View File

@ -1,157 +0,0 @@
/*
* Copyright (C) 2011 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.activity;
import com.android.email.R;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import android.app.Activity;
import android.app.ListFragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.AdapterView.OnItemClickListener;
/**
* Fragment containing a list of accounts to show during shortcut creation.
*/
public class AccountShortcutPickerFragment extends ListFragment
implements OnItemClickListener, LoaderCallbacks<Cursor> {
/**
* If true, creates pre-honeycomb style shortcuts. This allows developers to test launching
* the app from old style shortcuts (which point sat MessageList rather than Welcome) without
* actually carrying over shortcuts from previous versions.
*/
private final static boolean TEST_CREATE_OLD_STYLE_SHORTCUT = false; // DO NOT SUBMIT WITH TRUE
private final static String[] FROM_COLUMNS = new String[] {
AccountColumns.DISPLAY_NAME,
};
private final static int[] TO_IDS = new int[] {
android.R.id.text1,
};
private SimpleCursorAdapter mAdapter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ListView listView = getListView();
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(false);
String[] fromColumns;
fromColumns = FROM_COLUMNS;
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_expandable_list_item_1, null, fromColumns, TO_IDS, 0);
setListAdapter(mAdapter);
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor)parent.getItemAtPosition(position);
Account account = new Account();
account.restore(cursor);
setupShortcut(account);
getActivity().finish();
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(
getActivity(), Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// No accounts; close the dialog
if (data.getCount() == 0) {
getActivity().finish();
return;
}
mAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
/**
* This function creates a shortcut and returns it to the caller. There are actually two
* intents that you will send back.
*
* The first intent serves as a container for the shortcut and is returned to the launcher by
* setResult(). This intent must contain three fields:
*
* <ul>
* <li>{@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.</li>
* <li>{@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with
* the shortcut.</li>
* <li>{@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a
* bitmap, <i>or</i> {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as
* a drawable resource.</li>
* </ul>
*
* If you use a simple drawable resource, note that you must wrapper it using
* {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so
* that the launcher can access resources that are stored in your application's .apk file. If
* you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras
* bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}.
*
* The shortcut intent can be any intent that you wish the launcher to send, when the user
* clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW}
* with an appropriate Uri for your content, but any Intent will work here as long as it
* triggers the desired action within your Activity.
*/
private void setupShortcut(Account account) {
Activity myActivity = getActivity();
// First, set up the shortcut intent.
final Intent shortcutIntent;
if (TEST_CREATE_OLD_STYLE_SHORTCUT) {
shortcutIntent = MessageList.createFroyoIntent(myActivity, account);
Log.d(Logging.LOG_TAG, "Created old style intent: " + shortcutIntent);
} else {
shortcutIntent = Welcome.createAccountShortcutIntent(myActivity, account);
}
// Then, set up the container intent (the response to the caller)
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, account.getDisplayName());
Parcelable iconResource
= Intent.ShortcutIconResource.fromContext(myActivity, R.mipmap.ic_launcher_email);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
// Now, return the result to the launcher
myActivity.setResult(Activity.RESULT_OK, intent);
}
}

View File

@ -79,7 +79,7 @@ public class MessageList extends Activity {
/**
* Create a froyo/gingerbread style account shortcut intent. Used by unit tests and
* test code in {@link AccountShortcutPicker}.
* test code in {@link ShortcutPicker}.
*/
@VisibleForTesting
static Intent createFroyoIntent(Context context, Account account) {

View File

@ -17,6 +17,7 @@
package com.android.email.activity;
import com.android.email.R;
import com.android.email.activity.ShortcutPickerFragment.AccountShortcutPickerFragment;
import android.app.Activity;
import android.content.Intent;
@ -27,27 +28,31 @@ import android.view.View.OnClickListener;
/**
* This class implements a launcher shortcut for directly accessing a single account.
*/
public class AccountShortcutPicker extends Activity implements OnClickListener {
public class ShortcutPicker extends Activity implements OnClickListener {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// TODO Relax this test slightly in order to re-use this activity for widget creation
// finish() immediately if we aren't supposed to be here
if (!Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
// finish() immediately if we aren't supposed to be here
finish();
return;
}
// Set handler for the "cancel" button
setContentView(R.layout.account_shortcut_picker);
findViewById(R.id.cancel).setOnClickListener(this);
if (getFragmentManager().findFragmentById(R.id.shortcut_list) == null) {
// Load the account picking fragment
// NOTE: do not add to history as this is the first fragment in the flow
AccountShortcutPickerFragment fragment = new AccountShortcutPickerFragment();
getFragmentManager().beginTransaction().add(R.id.shortcut_list, fragment).commit();
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
@Override
public void onClick(View v) {
switch (v.getId()) {

View File

@ -0,0 +1,271 @@
/*
* Copyright (C) 2011 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.activity;
import com.android.email.R;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import android.app.Activity;
import android.app.ListFragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.AdapterView.OnItemClickListener;
// TODO restructure this into three classes -- a base class w/ two sub-classes. Intead of using
// selectingMailbox(), we'd just define the proper methods in the sub-class.
/**
* Fragment containing a list of accounts to show during shortcut creation.
*/
public abstract class ShortcutPickerFragment extends ListFragment
implements OnItemClickListener, LoaderCallbacks<Cursor> {
/**
* If true, creates pre-honeycomb style shortcuts. This allows developers to test launching
* the app from old style shortcuts (which point sat MessageList rather than Welcome) without
* actually carrying over shortcuts from previous versions.
*/
private final static boolean TEST_CREATE_OLD_STYLE_SHORTCUT = false; // DO NOT SUBMIT WITH TRUE
private final static int LOADER_ID = 0;
private final static int[] TO_VIEWS = new int[] {
android.R.id.text1,
};
/** Cursor adapter that provides either the account or mailbox list */
private SimpleCursorAdapter mAdapter;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
final String[] fromColumns = getFromColumns();
mAdapter = new SimpleCursorAdapter(activity,
android.R.layout.simple_expandable_list_item_1, null, fromColumns, TO_VIEWS, 0);
setListAdapter(mAdapter);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ListView listView = getListView();
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(false);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// No accounts; close the dialog
if (data.getCount() == 0) {
// TODO what's the proper handling if the mailbox list is '0'? display toast?
getActivity().finish();
return;
}
// TODO special handle case where there is one account or one mailbox; the user shouldn't
// be forced to select anything in either of those cases.
mAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
/** Returns the cursor columns to map into list */
abstract String[] getFromColumns();
/**
* This function creates a shortcut and returns it to the caller. There are actually two
* intents that you will send back.
*
* The first intent serves as a container for the shortcut and is returned to the launcher by
* setResult(). This intent must contain three fields:
*
* <ul>
* <li>{@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.</li>
* <li>{@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with
* the shortcut.</li>
* <li>{@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a
* bitmap, <i>or</i> {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as
* a drawable resource.</li>
* </ul>
*
* If you use a simple drawable resource, note that you must wrapper it using
* {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so
* that the launcher can access resources that are stored in your application's .apk file. If
* you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras
* bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}.
*
* The shortcut intent can be any intent that you wish the launcher to send, when the user
* clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW}
* with an appropriate Uri for your content, but any Intent will work here as long as it
* triggers the desired action within your Activity.
*/
void setupShortcut(Account account, long mailboxId) {
Activity myActivity = getActivity();
// First, set up the shortcut intent.
final Intent shortcutIntent;
if (TEST_CREATE_OLD_STYLE_SHORTCUT) {
shortcutIntent = MessageList.createFroyoIntent(myActivity, account);
Log.d(Logging.LOG_TAG, "Created old style intent: " + shortcutIntent);
} else {
String uuid = account.mCompatibilityUuid;
shortcutIntent = Welcome.createAccountShortcutIntent(myActivity, uuid, mailboxId);
}
// Then, set up the container intent (the response to the caller)
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, account.getDisplayName());
Parcelable iconResource
= Intent.ShortcutIconResource.fromContext(myActivity, R.mipmap.ic_launcher_email);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
// Now, return the result to the launcher
myActivity.setResult(Activity.RESULT_OK, intent);
}
/** Account picker */
public static class AccountShortcutPickerFragment extends ShortcutPickerFragment {
private final static String[] ACCOUNT_FROM_COLUMNS = new String[] {
AccountColumns.DISPLAY_NAME,
};
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().setTitle(R.string.account_shortcut_picker_title);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
Account account = new Account();
account.restore(cursor);
ShortcutPickerFragment fragment = new MailboxShortcutPickerFragment();
final Bundle args = new Bundle();
args.putParcelable(MailboxShortcutPickerFragment.ARG_ACCOUNT, account);
fragment.setArguments(args);
getFragmentManager()
.beginTransaction()
.replace(R.id.shortcut_list, fragment)
.addToBackStack(null)
.commit();
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Context context = getActivity();
// TODO Add ability to insert special account "all accounts"
return new CursorLoader(
context, Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, null);
}
@Override
String[] getFromColumns() {
return ACCOUNT_FROM_COLUMNS;
}
}
/** Mailbox picker */
public static class MailboxShortcutPickerFragment extends ShortcutPickerFragment {
static final String ARG_ACCOUNT = "MailboxShortcutPickerFragment.account";
private final static String[] MAILBOX_FROM_COLUMNS = new String[] {
MailboxColumns.DISPLAY_NAME,
};
/** Loader projection used for IMAP & POP3 accounts */
private final static String[] IMAP_PROJECTION = new String [] {
MailboxColumns.ID, MailboxColumns.SERVER_ID + " as " + MailboxColumns.DISPLAY_NAME
};
/** Loader projection used for EAS accounts */
private final static String[] EAS_PROJECTION = new String [] {
MailboxColumns.ID, MailboxColumns.DISPLAY_NAME
};
// TODO This is identical to MailboxesAdapter#ALL_MAILBOX_SELECTION; any way we can create a
// common selection? Move this to the Mailbox class?
private final static String ALL_MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" +
" AND " + Mailbox.USER_VISIBLE_MAILBOX_SELECTION;
/** The currently selected account */
private Account mAccount;
@Override
public void onAttach(Activity activity) {
// Need to setup the account first thing
mAccount = getArguments().getParcelable(ARG_ACCOUNT);
super.onAttach(activity);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().setTitle(R.string.mailbox_shortcut_picker_title);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
long mailboxId = cursor.getLong(Mailbox.CONTENT_ID_COLUMN);
setupShortcut(mAccount, mailboxId);
getActivity().finish();
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Context context = getActivity();
// TODO Add ability to insert special mailboxes like "starred", etc...
// TODO Create a fully-qualified path name for Exchange accounts [code should also work
// for MoveMessageToDialog.java]
HostAuth recvAuth = mAccount.getOrCreateHostAuthRecv(context);
final String[] projection;
final String orderBy;
if (recvAuth.isEasConnection()) {
projection = EAS_PROJECTION;
orderBy = MailboxColumns.DISPLAY_NAME;
} else {
projection = IMAP_PROJECTION;
orderBy = MailboxColumns.SERVER_ID;
}
return new CursorLoader(
context, Mailbox.CONTENT_URI, projection, ALL_MAILBOX_SELECTION,
new String[] { Long.toString(mAccount.mId) }, orderBy);
}
@Override
String[] getFromColumns() {
return MAILBOX_FROM_COLUMNS;
}
}
}

View File

@ -122,10 +122,11 @@ public class Welcome extends Activity {
* Create an {@link Intent} for account shortcuts. The returned intent stores the account's
* UUID rather than the account ID, which will be changed after account restore.
*/
public static Intent createAccountShortcutIntent(Context context, Account account) {
public static Intent createAccountShortcutIntent(Context context, String uuid, long mailboxId) {
final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder(
VIEW_MAILBOX_INTENT_URL_PATH);
IntentUtilities.setAccountUuid(b, account.mCompatibilityUuid);
IntentUtilities.setAccountUuid(b, uuid);
IntentUtilities.setMailboxId(b, mailboxId);
return IntentUtilities.createRestartAppIntent(b.build());
}