Bug 920170 - Allow adding a grid of icons to prompts. r=margaret

This commit is contained in:
Wes Johnston 2013-10-15 08:53:16 -07:00
parent 3a22f19819
commit 4068563efd
20 changed files with 421 additions and 10 deletions

View File

@ -132,6 +132,7 @@ FENNEC_JAVA_FILES = \
prompts/Prompt.java \
prompts/PromptInput.java \
prompts/PromptService.java \
prompts/IconGridInput.java \
Restarter.java \
sqlite/ByteBufferInputStream.java \
sqlite/MatrixBlobCursor.java \
@ -447,6 +448,8 @@ RES_LAYOUT = \
res/layout/home_banner.xml \
res/layout/home_suggestion_prompt.xml \
res/layout/home_top_sites_page.xml \
res/layout/icon_grid.xml \
res/layout/icon_grid_item.xml \
res/layout/web_app.xml \
res/layout/launch_app_list.xml \
res/layout/launch_app_listitem.xml \
@ -613,6 +616,8 @@ RES_DRAWABLE_MDPI = \
res/drawable-mdpi/bookmark_folder_closed.png \
res/drawable-mdpi/bookmark_folder_opened.png \
res/drawable-mdpi/desktop_notification.png \
res/drawable-mdpi/grid_icon_bg_activated.9.png \
res/drawable-mdpi/grid_icon_bg_focused.9.png \
res/drawable-mdpi/home_tab_menu_strip.9.png \
res/drawable-mdpi/ic_menu_addons_filler.png \
res/drawable-mdpi/ic_menu_bookmark_add.png \
@ -718,6 +723,8 @@ RES_DRAWABLE_HDPI = \
res/drawable-hdpi/folder.png \
res/drawable-hdpi/home_bg.png \
res/drawable-hdpi/home_star.png \
res/drawable-hdpi/grid_icon_bg_activated.9.png \
res/drawable-hdpi/grid_icon_bg_focused.9.png \
res/drawable-hdpi/abouthome_thumbnail.png \
res/drawable-hdpi/alert_addon.png \
res/drawable-hdpi/alert_app.png \
@ -830,6 +837,8 @@ RES_DRAWABLE_XHDPI = \
res/drawable-xhdpi/alert_mic_camera.png \
res/drawable-xhdpi/arrow_popup_bg.9.png \
res/drawable-xhdpi/home_tab_menu_strip.9.png \
res/drawable-xhdpi/grid_icon_bg_activated.9.png \
res/drawable-xhdpi/grid_icon_bg_focused.9.png \
res/drawable-xhdpi/ic_menu_addons_filler.png \
res/drawable-xhdpi/ic_menu_bookmark_add.png \
res/drawable-xhdpi/ic_menu_bookmark_remove.png \
@ -1079,6 +1088,7 @@ RES_DRAWABLE += \
res/drawable/url_bar_bg.xml \
res/drawable/url_bar_entry.xml \
res/drawable/url_bar_nav_button.xml \
res/drawable/icon_grid_item_bg.xml \
res/drawable/url_bar_right_edge.xml \
res/drawable/bookmark_folder.xml \
res/drawable/divider_horizontal.xml \

View File

@ -0,0 +1,172 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.prompts;
import org.json.JSONArray;
import org.json.JSONObject;
import org.mozilla.gecko.R;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.LayoutInflater;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.TextView;
import android.widget.ImageView;
import android.widget.ListView;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class IconGridInput extends PromptInput implements OnItemClickListener {
public static final String INPUT_TYPE = "icongrid";
public static final String LOGTAG = "GeckoIconGridInput";
private ArrayAdapter<IconGridItem> mAdapter; // An adapter holding a list of items to show in the grid
private static int mColumnWidth = -1; // The maximum width of columns
private static int mMaxColumns = -1; // The maximum number of columns to show
private static int mIconSize = -1; // Size of icons in the grid
private int mSelected = -1; // Current selection
private JSONArray mArray;
public IconGridInput(JSONObject obj) {
super(obj);
mArray = obj.optJSONArray("items");
}
@Override
public View getView(Context context) throws UnsupportedOperationException {
if (mColumnWidth < 0) {
// getColumnWidth isn't available on pre-ICS, so we pull it out and assign it here
mColumnWidth = context.getResources().getDimensionPixelSize(R.dimen.icongrid_columnwidth);
}
if (mIconSize < 0) {
mIconSize = GeckoAppShell.getPreferredIconSize();
}
if (mMaxColumns < 0) {
mMaxColumns = context.getResources().getInteger(R.integer.max_icon_grid_columns);
}
// TODO: Dynamically handle size changes
final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Display display = wm.getDefaultDisplay();
final int screenWidth = display.getWidth();
int maxColumns = Math.min(mMaxColumns, screenWidth / mColumnWidth);
final GridView view = (GridView) LayoutInflater.from(context).inflate(R.layout.icon_grid, null, false);
view.setColumnWidth(mColumnWidth);
final ArrayList<IconGridItem> items = new ArrayList<IconGridItem>(mArray.length());
for (int i = 0; i < mArray.length(); i++) {
IconGridItem item = new IconGridItem(context, mArray.optJSONObject(i));
items.add(item);
if (item.selected) {
mSelected = i;
view.setSelection(i);
}
}
view.setNumColumns(Math.min(items.size(), maxColumns));
view.setOnItemClickListener(this);
mAdapter = new IconGridAdapter(context, -1, items);
view.setAdapter(mAdapter);
mView = view;
return mView;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mSelected = position;
}
@Override
public String getValue() {
return Integer.toString(mSelected);
}
@Override
public boolean getScrollable() {
return true;
}
private class IconGridAdapter extends ArrayAdapter<IconGridItem> {
public IconGridAdapter(Context context, int resource, List<IconGridItem> items) {
super(context, resource, items);
}
@Override
public View getView(int position, View convert, ViewGroup parent) {
final Context context = parent.getContext();
if (convert == null) {
convert = LayoutInflater.from(context).inflate(R.layout.icon_grid_item, parent, false);
}
bindView(convert, context, position);
return convert;
}
private void bindView(View v, Context c, int position) {
final IconGridItem item = getItem(position);
final TextView text1 = (TextView) v.findViewById(android.R.id.text1);
text1.setText(item.label);
final TextView text2 = (TextView) v.findViewById(android.R.id.text2);
if (TextUtils.isEmpty(item.description)) {
text2.setVisibility(View.GONE);
} else {
text2.setVisibility(View.VISIBLE);
text2.setText(item.description);
}
final ImageView icon = (ImageView) v.findViewById(R.id.icon);
icon.setImageDrawable(item.icon);
ViewGroup.LayoutParams lp = icon.getLayoutParams();
lp.width = lp.height = mIconSize;
}
}
private class IconGridItem {
final String label;
final String description;
final boolean selected;
Drawable icon;
public IconGridItem(final Context context, final JSONObject obj) {
label = obj.optString("name");
final String iconUrl = obj.optString("iconUri");
description = obj.optString("description");
selected = obj.optBoolean("selected");
BitmapUtils.getDrawable(context, iconUrl, new BitmapUtils.BitmapLoader() {
public void onBitmapFound(Drawable d) {
icon = d;
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
});
}
}
}

View File

@ -317,9 +317,9 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
*
* @param builder
* the alert builder currently building this dialog.
* @return
* returns true if the inputs were added successfully. They may fail
* if the requested input is incompatible with this Android version
* @return
* return true if the inputs were added successfully. This may fail
* if the requested input is compatible with this Android verison
*/
private boolean addInputs(AlertDialog.Builder builder) {
int length = mInputs == null ? 0 : mInputs.length;
@ -328,19 +328,28 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
}
try {
View root = null;
boolean scrollable = false; // If any of the innuts are scrollable, we won't wrap this in a ScrollView
if (length == 1) {
ScrollView view = new ScrollView(mContext);
view.addView(mInputs[0].getView(mContext));
builder.setView(applyInputStyle(view));
root = mInputs[0].getView(mContext);
scrollable |= mInputs[0].getScrollable();
} else if (length > 1) {
LinearLayout linearLayout = new LinearLayout(mContext);
linearLayout.setOrientation(LinearLayout.VERTICAL);
for (int i = 0; i < length; i++) {
View content = mInputs[i].getView(mContext);
linearLayout.addView(content);
scrollable |= mInputs[i].getScrollable();
}
root = linearLayout;
}
if (scrollable) {
builder.setView(applyInputStyle(root));
} else {
ScrollView view = new ScrollView(mContext);
view.addView(linearLayout);
view.addView(root);
builder.setView(applyInputStyle(view));
}
} catch(Exception ex) {

View File

@ -42,8 +42,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
public class PromptInput {
private final JSONObject mJSONInput;
protected final String mLabel;
protected final String mType;
protected final String mId;
@ -329,7 +327,6 @@ public class PromptInput {
}
public PromptInput(JSONObject obj) {
mJSONInput = obj;
mLabel = obj.optString("label");
mType = obj.optString("type");
String id = obj.optString("id");
@ -351,6 +348,8 @@ public class PromptInput {
return new MenulistInput(obj);
} else if (LabelInput.INPUT_TYPE.equals(type)) {
return new LabelInput(obj);
} else if (IconGridInput.INPUT_TYPE.equals(type)) {
return new IconGridInput(obj);
} else {
for (String dtType : DateTimeInput.INPUT_TYPES) {
if (dtType.equals(type)) {
@ -372,4 +371,8 @@ public class PromptInput {
public String getValue() {
return "";
}
public boolean getScrollable() {
return false;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
android:state_pressed="true"
android:drawable="@drawable/grid_icon_bg_focused" />
<item android:state_activated="true"
android:drawable="@drawable/grid_icon_bg_activated" />
<item android:drawable="@android:color/transparent" />
</selector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:choiceMode="singleChoice"
android:padding="@dimen/icongrid_padding"/>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
//device/apps/common/res/any/layout/resolve_list_item.xml
Copyright 2006, 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:gravity="center"
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="@drawable/icon_grid_item_bg"
android:padding="16dp">
<!-- Extended activity info to distinguish between duplicate activity names -->
<TextView android:id="@android:id/text2"
android:textAppearance="?android:attr/textAppearance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minLines="2"
android:maxLines="2"
android:paddingTop="4dip"
android:paddingBottom="4dip" />
<!-- Activity icon when presenting dialog
Size will be filled in by ResolverActivity -->
<ImageView android:id="@+id/icon"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitCenter" />
<!-- Activity name -->
<TextView android:id="@android:id/text1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minLines="2"
android:maxLines="2"
android:paddingTop="4dip"
android:paddingBottom="4dip" />
</LinearLayout>

View File

@ -96,4 +96,8 @@
<!-- Banner -->
<dimen name="home_banner_height">72dp</dimen>
<!-- Icon Grid -->
<dimen name="icongrid_columnwidth">128dp</dimen>
<dimen name="icongrid_padding">16dp</dimen>
</resources>

View File

@ -7,5 +7,6 @@
<integer name="number_of_top_sites">6</integer>
<integer name="number_of_top_sites_cols">2</integer>
<integer name="max_icon_grid_columns">4</integer>
</resources>

View File

@ -20,6 +20,7 @@
[testMailToContextMenu]
[testPictureLinkContextMenu]
[testPasswordProvider]
[testPromptGridInput]
# [testPasswordEncrypt] # see bug 824067
[testFormHistory]
[testBrowserProvider]

View File

@ -0,0 +1,51 @@
<html>
<head>
<title>IconGrid test page</title>
<meta name="viewport" content="initial-scale=1.0"/>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript">
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/Prompt.jsm");
function start() {
var test = location.hash.substring(1);
window[test]();
}
function test1() {
var p = new Prompt({
title: "Prompt 1",
buttons: [
"OK"
],
}).addIconGrid({
items: [
{ iconUri: "drawable://alert_app", name: "Icon 1", selected: true },
{ iconUri: "drawable://alert_download", name: "Icon 2" },
{ iconUri: "drawable://alert_addon", name: "Icon 3" },
{ iconUri: "drawable://alert_addon", name: "Icon 4" },
{ iconUri: "drawable://alert_addon", name: "Icon 5" },
{ iconUri: "drawable://alert_addon", name: "Icon 6" },
{ iconUri: "drawable://alert_addon", name: "Icon 7" },
{ iconUri: "drawable://alert_addon", name: "Icon 8" },
{ iconUri: "drawable://alert_addon", name: "Icon 9" },
{ iconUri: "drawable://alert_addon", name: "Icon 10" },
{ iconUri: "drawable://alert_addon", name: "Icon 11" },
]
});
p.show(function(data) {
sendResult(data.icongrid0 == 10, "Got result " + data.icongrid0);
});
}
function sendResult(pass, message) {
setTimeout(function() {
alert((pass ? "PASS " : "FAIL ") + message);
}, 1000);
}
</script>
</head>
<body onload="start();">
</body>
</html>

View File

@ -0,0 +1,61 @@
#filter substitution
package @ANDROID_PACKAGE_NAME@.tests;
import @ANDROID_PACKAGE_NAME@.*;
import android.graphics.drawable.Drawable;
import android.widget.EditText;
import android.widget.CheckedTextView;
import android.widget.TextView;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.view.View;
import android.util.Log;
import org.json.JSONObject;
public class testPromptGridInput extends BaseTest {
@Override
protected int getTestType() {
return TEST_MOCHITEST;
}
protected int index = 1;
public void testPromptGridInput() {
blockForGeckoReady();
test(1);
testGridItem("Icon 1");
testGridItem("Icon 2");
testGridItem("Icon 3");
testGridItem("Icon 4");
testGridItem("Icon 5");
testGridItem("Icon 6");
testGridItem("Icon 7");
testGridItem("Icon 8");
testGridItem("Icon 9");
testGridItem("Icon 10");
testGridItem("Icon 11");
mSolo.clickOnText("Icon 11");
mSolo.clickOnText("OK");
mAsserter.ok(waitForText("PASS"), "test passed", "PASS");
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
}
public void testGridItem(String title) {
// Force the list to scroll if necessary
mSolo.waitForText(title, 1, 500, true);
mAsserter.ok(waitForText(title), "Found grid item", title);
}
public void test(final int num) {
// Load about:blank between each test to ensure we reset state
loadUrl("about:blank");
mAsserter.ok(waitForText("about:blank"), "Loaded blank page", "page title match");
loadUrl("chrome://roboextender/content/robocop_prompt_gridinput.html#test" + num);
}
}

View File

@ -122,6 +122,14 @@ Prompt.prototype = {
});
},
addIconGrid: function(aOptions) {
return this._addInput({
type: "icongrid",
items: aOptions.items,
id: aOptions.id
});
},
show: function(callback) {
this.callback = callback;
log("Sending message");