am e73a100f: am 445a9785: Merge "don\'t hardcode "mSurface" throughout our source code" into kraken
diff --git a/api/current.xml b/api/current.xml
index ded3d3b3..56dedaf3 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1780,6 +1780,17 @@
visibility="public"
>
</field>
+<field name="adapter"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843454"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="addStatesFromChildren"
type="int"
transient="false"
@@ -1989,6 +2000,17 @@
visibility="public"
>
</field>
+<field name="as"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843460"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="author"
type="int"
transient="false"
@@ -2649,6 +2671,17 @@
visibility="public"
>
</field>
+<field name="column"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843463"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="columnDelay"
type="int"
transient="false"
@@ -3815,6 +3848,17 @@
visibility="public"
>
</field>
+<field name="from"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843458"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="fromAlpha"
type="int"
transient="false"
@@ -3837,6 +3881,17 @@
visibility="public"
>
</field>
+<field name="fromValue"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843461"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="fromXDelta"
type="int"
transient="false"
@@ -7225,6 +7280,17 @@
visibility="public"
>
</field>
+<field name="selection"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843455"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="settingsActivity"
type="int"
transient="false"
@@ -7412,6 +7478,17 @@
visibility="public"
>
</field>
+<field name="sortOrder"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843456"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="soundEffectsEnabled"
type="int"
transient="false"
@@ -8600,6 +8677,17 @@
visibility="public"
>
</field>
+<field name="to"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843459"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="toAlpha"
type="int"
transient="false"
@@ -8622,6 +8710,17 @@
visibility="public"
>
</field>
+<field name="toValue"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843462"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="toXDelta"
type="int"
transient="false"
@@ -8798,6 +8897,17 @@
visibility="public"
>
</field>
+<field name="uri"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843457"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="useLevel"
type="int"
transient="false"
@@ -9348,6 +9458,28 @@
visibility="public"
>
</field>
+<field name="withClass"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843465"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="withExpression"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843464"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="writePermission"
type="int"
transient="false"
@@ -34598,6 +34730,17 @@
visibility="public"
>
</field>
+<field name="STORAGE_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""storage""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="TELEPHONY_SERVICE"
type="java.lang.String"
transient="false"
@@ -54258,6 +54401,19 @@
<parameter name="lockingEnabled" type="boolean">
</parameter>
</method>
+<method name="setMaxSqlCacheSize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cacheSize" type="int">
+</parameter>
+</method>
<method name="setMaximumSize"
return="long"
abstract="false"
@@ -54460,6 +54616,17 @@
visibility="public"
>
</field>
+<field name="MAX_SQL_CACHE_SIZE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="100"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="NO_LOCALIZED_COLLATORS"
type="int"
transient="false"
@@ -69318,6 +69485,17 @@
visibility="public"
>
</method>
+<method name="computeConstantSize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
<method name="getChangingConfigurations"
return="int"
abstract="false"
@@ -70212,6 +70390,36 @@
</parameter>
</method>
</class>
+<class name="MipmapDrawable"
+ extends="android.graphics.drawable.DrawableContainer"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="MipmapDrawable"
+ type="android.graphics.drawable.MipmapDrawable"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="addDrawable"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="drawable" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+</class>
<class name="NinePatchDrawable"
extends="android.graphics.drawable.Drawable"
abstract="false"
@@ -120547,6 +120755,240 @@
</method>
</class>
</package>
+<package name="android.os.storage"
+>
+<class name="StorageEventListener"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StorageEventListener"
+ type="android.os.storage.StorageEventListener"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onStorageStateChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="path" type="java.lang.String">
+</parameter>
+<parameter name="oldState" type="java.lang.String">
+</parameter>
+<parameter name="newState" type="java.lang.String">
+</parameter>
+</method>
+<method name="onUsbMassStorageConnectionChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="connected" type="boolean">
+</parameter>
+</method>
+</class>
+<class name="StorageManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="disableUsbMassStorage"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="enableUsbMassStorage"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isUsbMassStorageConnected"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isUsbMassStorageEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="registerListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.os.storage.StorageEventListener">
+</parameter>
+</method>
+<method name="unregisterListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.os.storage.StorageEventListener">
+</parameter>
+</method>
+</class>
+<class name="StorageResultCode"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StorageResultCode"
+ type="android.os.storage.StorageResultCode"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<field name="OperationFailedInternalError"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedMediaBlank"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedMediaCorrupt"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedNoMedia"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageBusy"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageMounted"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageNotMounted"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationSucceeded"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+</package>
<package name="android.preference"
>
<class name="CheckBoxPreference"
@@ -197548,6 +197990,195 @@
</parameter>
</method>
</interface>
+<class name="Adapters"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters"
+ type="android.widget.Adapters"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="loadAdapter"
+ return="android.widget.BaseAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+<method name="loadCursorAdapter"
+ return="android.widget.CursorAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="uri" type="java.lang.String">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+<method name="loadCursorAdapter"
+ return="android.widget.CursorAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+</class>
+<class name="Adapters.CursorBinder"
+ extends="java.lang.Object"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters.CursorBinder"
+ type="android.widget.Adapters.CursorBinder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="transformation" type="android.widget.Adapters.CursorTransformation">
+</parameter>
+</constructor>
+<method name="bind"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<field name="mContext"
+ type="android.content.Context"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+<field name="mTransformation"
+ type="android.widget.Adapters.CursorTransformation"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+</class>
+<class name="Adapters.CursorTransformation"
+ extends="java.lang.Object"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters.CursorTransformation"
+ type="android.widget.Adapters.CursorTransformation"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<method name="transform"
+ return="java.lang.String"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="transformToResource"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<field name="mContext"
+ type="android.content.Context"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+</class>
<class name="AlphabetIndexer"
extends="android.database.DataSetObserver"
abstract="false"
@@ -199509,6 +200140,20 @@
<parameter name="autoRequery" type="boolean">
</parameter>
</constructor>
+<constructor name="CursorAdapter"
+ type="android.widget.CursorAdapter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</constructor>
<method name="bindView"
return="void"
abstract="true"
@@ -199656,6 +200301,23 @@
<parameter name="autoRequery" type="boolean">
</parameter>
</method>
+<method name="init"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
<method name="newDropDownView"
return="android.view.View"
abstract="false"
@@ -199727,6 +200389,28 @@
<parameter name="filterQueryProvider" type="android.widget.FilterQueryProvider">
</parameter>
</method>
+<field name="FLAG_AUTO_REQUERY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FLAG_REGISTER_CONTENT_OBSERVER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
</class>
<class name="CursorTreeAdapter"
extends="android.widget.BaseExpandableListAdapter"
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a556a32..ba9f005 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1447,7 +1447,7 @@
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
- private static final String DB_INFO_FORMAT = " %8d %8d %10d %s";
+ private static final String DB_INFO_FORMAT = " %4d %6d %8d %14s %s";
// Formatting for checkin service - update version if row format changes
private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
@@ -1847,7 +1847,7 @@
for (int i = 0; i < stats.dbStats.size(); i++) {
DbStats dbStats = stats.dbStats.get(i);
printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
- dbStats.lookaside, dbStats.dbName);
+ dbStats.lookaside, dbStats.cache, dbStats.dbName);
pw.print(',');
}
@@ -1898,11 +1898,12 @@
int N = stats.dbStats.size();
if (N > 0) {
pw.println(" DATABASES");
- printRow(pw, " %8s %8s %10s %s", "Pagesize", "Dbsize", "Lookaside", "Dbname");
+ printRow(pw, " %4s %6s %8s %14s %s", "pgsz", "dbsz", "lkaside", "cache",
+ "Dbname");
for (int i = 0; i < N; i++) {
DbStats dbStats = stats.dbStats.get(i);
printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
- dbStats.lookaside, dbStats.dbName);
+ dbStats.lookaside, dbStats.cache, dbStats.dbName);
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 30822d4..0afd6d2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1372,7 +1372,6 @@
public static final String SENSOR_SERVICE = "sensor";
/**
- * @hide
* Use with {@link #getSystemService} to retrieve a {@link
* android.os.storage.StorageManager} for accesssing system storage
* functions.
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 455815f..18f69af 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,6 +16,8 @@
package android.content;
+import com.google.android.collect.Maps;
+
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
@@ -55,6 +57,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
@@ -126,14 +129,13 @@
private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
- private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
+ private static final String SYNC_WAKE_LOCK_PREFIX = "SyncWakeLock";
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
private Context mContext;
private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
- volatile private PowerManager.WakeLock mSyncWakeLock;
volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
@@ -195,6 +197,8 @@
private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0];
+ private final PowerManager mPowerManager;
+
public void onAccountsUpdated(Account[] accounts) {
// remember if this was the first time this was called after an update
final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
@@ -356,15 +360,13 @@
} else {
mNotificationMgr = null;
}
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK);
- mSyncWakeLock.setReferenceCounted(false);
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
// This WakeLock is used to ensure that we stay awake between the time that we receive
// a sync alarm notification and when we finish processing it. We need to do this
// because we don't do the work in the alarm handler, rather we do it in a message
// handler.
- mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
HANDLE_SYNC_ALARM_WAKE_LOCK);
mHandleAlarmWakeLock.setReferenceCounted(false);
@@ -1302,6 +1304,9 @@
public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
private Long mAlarmScheduleTime = null;
public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
+ private PowerManager.WakeLock mSyncWakeLock;
+ private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks =
+ Maps.newHashMap();
// used to track if we have installed the error notification so that we don't reinstall
// it if sync is still failing
@@ -1315,6 +1320,18 @@
}
}
+ private PowerManager.WakeLock getSyncWakeLock(String accountType, String authority) {
+ final Pair<String, String> wakeLockKey = Pair.create(accountType, authority);
+ PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
+ if (wakeLock == null) {
+ final String name = SYNC_WAKE_LOCK_PREFIX + "_" + authority + "_" + accountType;
+ wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
+ wakeLock.setReferenceCounted(false);
+ mWakeLocks.put(wakeLockKey, wakeLock);
+ }
+ return wakeLock;
+ }
+
private void waitUntilReadyToRun() {
CountDownLatch latch = mReadyToRunLatch;
if (latch != null) {
@@ -1477,8 +1494,9 @@
}
} finally {
final boolean isSyncInProgress = mActiveSyncContext != null;
- if (!isSyncInProgress) {
+ if (!isSyncInProgress && mSyncWakeLock != null) {
mSyncWakeLock.release();
+ mSyncWakeLock = null;
}
manageSyncNotification();
manageErrorNotification();
@@ -1708,7 +1726,26 @@
return;
}
- mSyncWakeLock.acquire();
+ // Find the wakelock for this account and authority and store it in mSyncWakeLock.
+ // Be sure to release the previous wakelock so that we don't end up with it being
+ // held until it is used again.
+ // There are a couple tricky things about this code:
+ // - make sure that we acquire the new wakelock before releasing the old one,
+ // otherwise the device might go to sleep as soon as we release it.
+ // - since we use non-reference counted wakelocks we have to be sure not to do
+ // the release if the wakelock didn't change. Othewise we would do an
+ // acquire followed by a release on the same lock, resulting in no lock
+ // being held.
+ PowerManager.WakeLock oldWakeLock = mSyncWakeLock;
+ try {
+ mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority);
+ mSyncWakeLock.acquire();
+ } finally {
+ if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) {
+ oldWakeLock.release();
+ }
+ }
+
// no need to schedule an alarm, as that will be done by our caller.
// the next step will occur when we get either a timeout or a
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 3b7416e..01c8a38 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -268,10 +268,15 @@
*/
/* package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
/**
- * @hide
+ * absolute max value that can set by {@link #setMaxSqlCacheSize(int)}
+ * size of each prepared-statement is between 1K - 6K, depending on the complexity of the
+ * sql statement & schema.
*/
- public static final int MAX_SQL_CACHE_SIZE = 250;
- private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance
+ public static final int MAX_SQL_CACHE_SIZE = 100;
+
+ // default statement-cache size per database connection ( = instance of this class)
+ private int mMaxSqlCacheSize = 25;
+
private int mCacheFullWarnings;
private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1;
@@ -1975,14 +1980,6 @@
* mapping is NOT replaced with the new mapping).
*/
/* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
- if (mMaxSqlCacheSize == 0) {
- // for this database, there is no cache of compiled sql.
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql);
- }
- return;
- }
-
SQLiteCompiledSql compiledSql = null;
synchronized(mCompiledQueries) {
// don't insert the new mapping if a mapping already exists
@@ -2036,13 +2033,6 @@
SQLiteCompiledSql compiledStatement = null;
boolean cacheHit;
synchronized(mCompiledQueries) {
- if (mMaxSqlCacheSize == 0) {
- // for this database, there is no cache of compiled sql.
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|cache NOT found|" + getPath());
- }
- return null;
- }
cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
}
if (cacheHit) {
@@ -2099,19 +2089,17 @@
}
/**
- * set the max size of the compiled sql cache for this database after purging the cache.
+ * set the max size of the prepared-statement cache for this database.
* (size of the cache = number of compiled-sql-statements stored in the cache).
*
- * max cache size can ONLY be increased from its current size (default = 0).
+ * max cache size can ONLY be increased from its current size (default = 10).
* if this method is called with smaller size than the current value of mMaxSqlCacheSize,
* then IllegalStateException is thrown
*
* synchronized because we don't want t threads to change cache size at the same time.
- * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE)
- * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or
- * < the value set with previous setMaxSqlCacheSize() call.
- *
- * @hide
+ * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE})
+ * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or
+ * > the value set with previous setMaxSqlCacheSize() call.
*/
public synchronized void setMaxSqlCacheSize(int cacheSize) {
if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
@@ -2176,7 +2164,8 @@
}
if (pageCount > 0) {
dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
- lookasideUsed));
+ lookasideUsed, db.mNumCacheHits, db.mNumCacheMisses,
+ db.mCompiledQueries.size()));
}
}
}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index a4db6d9..2970c9e 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -134,11 +134,16 @@
/** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
public int lookaside;
- public DbStats(String dbName, long pageCount, long pageSize, int lookaside) {
+ /** statement cache stats: hits/misses/cachesize */
+ public String cache;
+
+ public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
+ int hits, int misses, int cachesize) {
this.dbName = dbName;
- this.pageSize = pageSize;
+ this.pageSize = pageSize / 1024;
dbSize = (pageCount * pageSize) / 1024;
this.lookaside = lookaside;
+ this.cache = hits + "/" + misses + "/" + cachesize;
}
}
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index 7b883a7..d3d39d6 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -18,7 +18,6 @@
/**
* Used for receiving notifications from the StorageManager
- * @hide
*/
public abstract class StorageEventListener {
/**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a12603c..b49979c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -45,8 +45,6 @@
* {@link android.content.Context#getSystemService(java.lang.String)} with an argument
* of {@link android.content.Context#STORAGE_SERVICE}.
*
- * @hide
- *
*/
public class StorageManager
diff --git a/core/java/android/os/storage/StorageResultCode.java b/core/java/android/os/storage/StorageResultCode.java
index 075f47f..07d95df 100644
--- a/core/java/android/os/storage/StorageResultCode.java
+++ b/core/java/android/os/storage/StorageResultCode.java
@@ -19,8 +19,6 @@
/**
* Class that provides access to constants returned from StorageManager
* and lower level MountService APIs.
- *
- * @hide
*/
public class StorageResultCode
{
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index 635323e..282417d 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -181,7 +181,9 @@
boolean inUtc = start.parse(dtstart);
boolean allDay = start.allDay;
- if (inUtc) {
+ // We force TimeZone to UTC for "all day recurring events" as the server is sending no
+ // TimeZone in DTSTART for them
+ if (inUtc || allDay) {
tzid = Time.TIMEZONE_UTC;
}
@@ -204,10 +206,7 @@
}
if (allDay) {
- // TODO: also change tzid to be UTC? that would be consistent, but
- // that would not reflect the original timezone value back to the
- // server.
- start.timezone = Time.TIMEZONE_UTC;
+ start.timezone = Time.TIMEZONE_UTC;
}
long millis = start.toMillis(false /* use isDst */);
values.put(Calendar.Events.DTSTART, millis);
diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java
index 875c29e..b37b07d 100644
--- a/core/java/android/pim/vcard/JapaneseUtils.java
+++ b/core/java/android/pim/vcard/JapaneseUtils.java
@@ -366,11 +366,11 @@
}
/**
- * Return half-width version of that character if possible. Return null if not possible
+ * Returns half-width version of that character if possible. Returns null if not possible
* @param ch input character
* @return CharSequence object if the mapping for ch exists. Return null otherwise.
*/
- public static String tryGetHalfWidthText(char ch) {
+ public static String tryGetHalfWidthText(final char ch) {
if (sHalfWidthMap.containsKey(ch)) {
return sHalfWidthMap.get(ch);
} else {
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index 0a6415d..9b8cfbb3 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -47,7 +47,23 @@
import java.util.Set;
/**
- * The class which lets users create their own vCard String.
+ * <p>
+ * The class which lets users create their own vCard String. Typical usage is as follows:
+ * </p>
+ * <pre class="prettyprint">final VCardBuilder builder = new VCardBuilder(vcardType);
+ * builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ * .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ * .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ * .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ * .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ * .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ * .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ * .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ * .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ * .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ * .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ * .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ * return builder.toString();</pre>
*/
public class VCardBuilder {
private static final String LOG_TAG = "VCardBuilder";
@@ -75,13 +91,14 @@
private static final String VCARD_WS = " ";
private static final String VCARD_PARAM_EQUAL = "=";
- private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
- private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+ private static final String VCARD_PARAM_ENCODING_QP =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_QP;
+ private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64;
+ private static final String VCARD_PARAM_ENCODING_BASE64_V30 =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_B;
private static final String SHIFT_JIS = "SHIFT_JIS";
- private static final String UTF_8 = "UTF-8";
private final int mVCardType;
@@ -92,21 +109,28 @@
private final boolean mShouldUseQuotedPrintable;
private final boolean mUsesAndroidProperty;
private final boolean mUsesDefactProperty;
- private final boolean mUsesUtf8;
- private final boolean mUsesShiftJis;
private final boolean mAppendTypeParamName;
private final boolean mRefrainsQPToNameProperties;
private final boolean mNeedsToConvertPhoneticString;
private final boolean mShouldAppendCharsetParam;
- private final String mCharsetString;
+ private final String mCharset;
private final String mVCardCharsetParameter;
private StringBuilder mBuilder;
private boolean mEndAppended;
public VCardBuilder(final int vcardType) {
+ // Default charset should be used
+ this(vcardType, null);
+ }
+
+ /**
+ * @param vcardType
+ * @param charset If null, we use default charset for export.
+ */
+ public VCardBuilder(final int vcardType, String charset) {
mVCardType = vcardType;
mIsV30 = VCardConfig.isV30(vcardType);
@@ -116,40 +140,77 @@
mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
- mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
- mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
- mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8);
+ final boolean shouldUseUtf8 = VCardConfig.shouldUseUtf8ForExport(vcardType);
+ final boolean shouldUseShiftJis = VCardConfig.shouldUseShiftJisForExport(vcardType);
- if (mIsDoCoMo) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
+ // vCard 2.1 requires charset.
+ // vCard 3.0 does not allow it but we found some devices use it to determine
+ // the exact charset.
+ // We currently append it only when charset other than UTF_8 is used.
+ mShouldAppendCharsetParam = !(mIsV30 && shouldUseUtf8);
+
+ if (VCardConfig.isDoCoMo(vcardType) || shouldUseShiftJis) {
+ if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
+ Log.w(LOG_TAG,
+ "The charset \"" + charset + "\" is used while "
+ + SHIFT_JIS + " is needed to be used.");
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = SHIFT_JIS;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
+ } else {
+ if (mIsDoCoMo) {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "DoCoMo-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "Career-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ }
+ mCharset = charset;
}
- mCharsetString = charset;
- // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
- // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
- // Android, not shown to the public).
- mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
- } else if (mUsesShiftJis) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
- }
- mCharsetString = charset;
mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else {
- mCharsetString = UTF_8;
- mVCardCharsetParameter = "CHARSET=" + UTF_8;
+ if (TextUtils.isEmpty(charset)) {
+ Log.i(LOG_TAG,
+ "Use the charset \"" + VCardConfig.DEFAULT_EXPORT_CHARSET
+ + "\" for export.");
+ mCharset = VCardConfig.DEFAULT_EXPORT_CHARSET;
+ mVCardCharsetParameter = "CHARSET=" + VCardConfig.DEFAULT_EXPORT_CHARSET;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ mVCardCharsetParameter = "CHARSET=" + charset;
+ }
}
clear();
}
@@ -379,8 +440,8 @@
mBuilder.append(VCardConstants.PROPERTY_FN);
// Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
- // when it would be useful for external importers, assuming no external
- // importer allows this vioration.
+ // when it would be useful or necessary for external importers,
+ // assuming the external importer allows this vioration of the spec.
if (shouldAppendCharsetParam(displayName)) {
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(mVCardCharsetParameter);
@@ -454,18 +515,18 @@
mBuilder.append(VCARD_END_OF_LINE);
} else if (mIsJapaneseMobilePhone) {
// Note: There is no appropriate property for expressing
- // phonetic name in vCard 2.1, while there is in
+ // phonetic name (Yomigana in Japanese) in vCard 2.1, while there is in
// vCard 3.0 (SORT-STRING).
- // We chose to use DoCoMo's way when the device is Japanese one
- // since it is supported by
- // a lot of Japanese mobile phones. This is "X-" property, so
- // any parser hopefully would not get confused with this.
+ // We use DoCoMo's way when the device is Japanese one since it is already
+ // supported by a lot of Japanese mobile phones.
+ // This is "X-" property, so any parser hopefully would not get
+ // confused with this.
//
// Also, DoCoMo's specification requires vCard composer to use just the first
// column.
// i.e.
- // o SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
- // x SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+ // good: SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+ // bad : SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
mBuilder.append(VCardConstants.PROPERTY_SOUND);
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
@@ -519,10 +580,10 @@
mBuilder.append(encodedPhoneticGivenName);
}
}
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // family;given
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // given;middle
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // middle;prefix
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // prefix;suffix
mBuilder.append(VCARD_END_OF_LINE);
}
@@ -549,7 +610,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticGivenName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticMiddleName)) {
final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable &&
@@ -572,7 +633,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticMiddleName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticFamilyName)) {
final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable &&
@@ -595,7 +656,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticFamilyName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticFamilyName))
}
}
@@ -642,22 +703,18 @@
if (TextUtils.isEmpty(phoneNumber)) {
continue;
}
- int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
- if (type == Phone.TYPE_PAGER) {
+
+ // PAGER number needs unformatted "phone number".
+ final int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+ if (type == Phone.TYPE_PAGER ||
+ VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
phoneLineExists = true;
if (!phoneSet.contains(phoneNumber)) {
phoneSet.add(phoneNumber);
appendTelLine(type, label, phoneNumber, isPrimary);
}
} else {
- // The entry "may" have several phone numbers when the contact entry is
- // corrupted because of its original source.
- //
- // e.g. I encountered the entry like the following.
- // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
- // This kind of entry is not able to be inserted via Android devices, but
- // possible if the source of the data is already corrupted.
- List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+ final List<String> phoneNumberList = splitAndTrimPhoneNumbers(phoneNumber);
if (phoneNumberList.isEmpty()) {
continue;
}
@@ -670,7 +727,7 @@
phoneSet.add(actualPhoneNumber);
appendTelLine(type, label, formattedPhoneNumber, isPrimary);
}
- }
+ } // for (String actualPhoneNumber : phoneNumberList) {
}
}
}
@@ -682,15 +739,38 @@
return this;
}
- private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
- List<String> phoneList = new ArrayList<String>();
+ /**
+ * <p>
+ * Splits a given string expressing phone numbers into several strings, and remove
+ * unnecessary characters inside them. The size of a returned list becomes 1 when
+ * no split is needed.
+ * </p>
+ * <p>
+ * The given number "may" have several phone numbers when the contact entry is corrupted
+ * because of its original source.
+ * e.g. "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami)"
+ * </p>
+ * <p>
+ * This kind of "phone numbers" will not be created with Android vCard implementation,
+ * but we may encounter them if the source of the input data has already corrupted
+ * implementation.
+ * </p>
+ * <p>
+ * To handle this case, this method first splits its input into multiple parts
+ * (e.g. "111-222-3333 (Miami)", "444-555-6666 (Broward", and 305653-6796 (Miami)") and
+ * removes unnecessary strings like "(Miami)".
+ * </p>
+ * <p>
+ * Do not call this method when trimming is inappropriate for its receivers.
+ * </p>
+ */
+ private List<String> splitAndTrimPhoneNumbers(final String phoneNumber) {
+ final List<String> phoneList = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
final int length = phoneNumber.length();
for (int i = 0; i < length; i++) {
final char ch = phoneNumber.charAt(i);
- // TODO: add a test case for string with '+', and care the other possible issues
- // which may happen by ignoring non-digits other than '+'.
if (Character.isDigit(ch) || ch == '+') {
builder.append(ch);
} else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
@@ -903,21 +983,21 @@
encodedCountry = escapeCharacters(rawCountry);
encodedNeighborhood = escapeCharacters(rawNeighborhood);
}
- final StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(encodedPoBox);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedStreet);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedLocality);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedRegion);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedPostalCode);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedCountry);
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(encodedPoBox);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(encodedStreet);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(encodedLocality);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(encodedRegion);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(encodedPostalCode);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
+ addressBuilder.append(encodedCountry);
return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
} else { // VCardUtils.areAllEmpty(rawAddressArray) == true
// Try to use FORMATTED_ADDRESS instead.
final String rawFormattedAddress =
@@ -940,16 +1020,16 @@
// We use the second value ("Extended Address") just because Japanese mobile phones
// do so. If the other importer expects the value be in the other field, some flag may
// be needed.
- final StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedFormattedAddress);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(encodedFormattedAddress);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
}
}
@@ -1146,6 +1226,8 @@
}
public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+ // There's possibility where a given object may have more than one birthday, which
+ // is inappropriate. We just build one birthday.
if (contentValuesList != null) {
String primaryBirthday = null;
String secondaryBirthday = null;
@@ -1213,16 +1295,19 @@
return this;
}
+ /**
+ * @param emitEveryTime If true, builder builds the line even when there's no entry.
+ */
public void appendPostalLine(final int type, final String label,
final ContentValues contentValues,
- final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean isPrimary, final boolean emitEveryTime) {
final boolean reallyUseQuotedPrintable;
final boolean appendCharset;
final String addressValue;
{
PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
if (postalStruct == null) {
- if (emitLineEveryTime) {
+ if (emitEveryTime) {
reallyUseQuotedPrintable = false;
appendCharset = false;
addressValue = "";
@@ -1537,7 +1622,8 @@
mBuilder.append(VCARD_END_OF_LINE);
}
- public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
+ public void appendAndroidSpecificProperty(
+ final String mimeType, ContentValues contentValues) {
if (!sAllowedAndroidPropertySet.contains(mimeType)) {
return;
}
@@ -1659,7 +1745,7 @@
encodedValue = encodeQuotedPrintable(rawValue);
} else {
// TODO: one line may be too huge, which may be invalid in vCard spec, though
- // several (even well-known) applications do not care this.
+ // several (even well-known) applications do not care that violation.
encodedValue = escapeCharacters(rawValue);
}
@@ -1794,9 +1880,9 @@
byte[] strArray = null;
try {
- strArray = str.getBytes(mCharsetString);
+ strArray = str.getBytes(mCharset);
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+ Log.e(LOG_TAG, "Charset " + mCharset + " cannot be used. "
+ "Try default charset");
strArray = str.getBytes();
}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index dc0d864..1bc7785 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -41,6 +41,7 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
import android.util.CharsetUtils;
import android.util.Log;
@@ -61,15 +62,11 @@
/**
* <p>
- * The class for composing VCard from Contacts information. Note that this is
- * completely differnt implementation from
- * android.syncml.pim.vcard.VCardComposer, which is not maintained anymore.
+ * The class for composing vCard from Contacts information.
* </p>
- *
* <p>
* Usually, this class should be used like this.
* </p>
- *
* <pre class="prettyprint">VCardComposer composer = null;
* try {
* composer = new VCardComposer(context);
@@ -94,14 +91,17 @@
* composer.terminate();
* }
* } </pre>
+ * <P>
+ * Users have to manually take care of memory efficiency. Even one vCard may contain
+ * image of non-trivial size for mobile devices.
+ * </P>
+ * <P>
+ * In default, Default {@link VCardBuilder} class is used to build each vCard.
+ * </P>
*/
public class VCardComposer {
private static final String LOG_TAG = "VCardComposer";
- public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
- public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
- public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
-
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information";
@@ -119,6 +119,8 @@
public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+ // Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
+ // since usual vCard devices for Japanese devices already use it.
private static final String SHIFT_JIS = "SHIFT_JIS";
private static final String UTF_8 = "UTF-8";
@@ -141,7 +143,7 @@
sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
- // Google talk is a special case.
+ // We don't add Google talk here since it has to be handled separately.
}
public static interface OneEntryHandler {
@@ -152,37 +154,37 @@
/**
* <p>
- * An useful example handler, which emits VCard String to outputstream one by one.
+ * An useful handler for emitting vCard String to an OutputStream object one by one.
* </p>
* <p>
* The input OutputStream object is closed() on {@link #onTerminate()}.
- * Must not close the stream outside.
+ * Must not close the stream outside this class.
* </p>
*/
public class HandlerForOutputStream implements OneEntryHandler {
@SuppressWarnings("hiding")
private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream";
- final private OutputStream mOutputStream; // mWriter will close this.
- private Writer mWriter;
-
private boolean mOnTerminateIsCalled = false;
+ final private OutputStream mOutputStream; // mWriter will close this.
+ protected Writer mWriter;
+
/**
* Input stream will be closed on the detruction of this object.
*/
- public HandlerForOutputStream(OutputStream outputStream) {
+ public HandlerForOutputStream(final OutputStream outputStream) {
mOutputStream = outputStream;
}
- public boolean onInit(Context context) {
+ public final boolean onInit(final Context context) {
try {
mWriter = new BufferedWriter(new OutputStreamWriter(
- mOutputStream, mCharsetString));
+ mOutputStream, mCharset));
} catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString);
+ Log.e(LOG_TAG, "Unsupported charset: " + mCharset);
mErrorReason = "Encoding is not supported (usually this does not happen!): "
- + mCharsetString;
+ + mCharset;
return false;
}
@@ -205,7 +207,7 @@
return true;
}
- public boolean onEntryCreated(String vcard) {
+ public final boolean onEntryCreated(String vcard) {
try {
mWriter.write(vcard);
} catch (IOException e) {
@@ -218,7 +220,7 @@
return true;
}
- public void onTerminate() {
+ public final void onTerminate() {
mOnTerminateIsCalled = true;
if (mWriter != null) {
try {
@@ -235,14 +237,21 @@
"IOException during closing the output stream: "
+ e.getMessage());
} finally {
- try {
- mWriter.close();
- } catch (IOException e) {
- }
+ closeOutputStream();
}
}
}
+ // Users can override this if they want to (e.g. if they don't want to close the stream).
+ // TODO: Should expose bare OutputStream instead?
+ public void closeOutputStream() {
+ try {
+ mWriter.close();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
+ }
+ }
+
@Override
public void finalize() {
if (!mOnTerminateIsCalled) {
@@ -257,11 +266,10 @@
private final ContentResolver mContentResolver;
private final boolean mIsDoCoMo;
- private final boolean mUsesShiftJis;
private Cursor mCursor;
private int mIdColumn;
- private final String mCharsetString;
+ private final String mCharset;
private boolean mTerminateIsCalled;
private final List<OneEntryHandler> mHandlerList;
@@ -272,11 +280,14 @@
};
public VCardComposer(Context context) {
- this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
}
+ /**
+ * The variant which sets charset to null and sets careHandlerErrors to true.
+ */
public VCardComposer(Context context, int vcardType) {
- this(context, vcardType, true);
+ this(context, vcardType, null, true);
}
public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
@@ -284,40 +295,88 @@
}
/**
- * Construct for supporting call log entry vCard composing.
+ * The variant which sets charset to null.
*/
public VCardComposer(final Context context, final int vcardType,
final boolean careHandlerErrors) {
+ this(context, vcardType, null, careHandlerErrors);
+ }
+
+ /**
+ * Construct for supporting call log entry vCard composing.
+ *
+ * @param context Context to be used during the composition.
+ * @param vcardType The type of vCard, typically available via {@link VCardConfig}.
+ * @param charset The charset to be used. Use null when you don't need the charset.
+ * @param careHandlerErrors If true, This object returns false everytime
+ * a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false.
+ * If false, this ignores those errors.
+ */
+ public VCardComposer(final Context context, final int vcardType,
+ String charset,
+ final boolean careHandlerErrors) {
mContext = context;
mVCardType = vcardType;
mCareHandlerErrors = careHandlerErrors;
mContentResolver = context.getContentResolver();
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
- if (mIsDoCoMo) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
+ if (mIsDoCoMo || VCardConfig.shouldUseShiftJisForExport(vcardType)) {
+ if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
+ Log.w(LOG_TAG,
+ "The charset \"" + charset + "\" is used while "
+ + SHIFT_JIS + " is needed to be used.");
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = SHIFT_JIS;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
+ } else {
+ if (mIsDoCoMo) {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "DoCoMo-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "Career-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ }
+ mCharset = charset;
}
- mCharsetString = charset;
- } else if (mUsesShiftJis) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
- }
- mCharsetString = charset;
} else {
- mCharsetString = UTF_8;
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = UTF_8;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
}
+ Log.d(LOG_TAG, "use the charset \"" + mCharset + "\"");
}
/**
@@ -351,7 +410,7 @@
}
if (mCareHandlerErrors) {
- List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+ final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
for (OneEntryHandler handler : mHandlerList) {
if (!handler.onInit(mContext)) {
@@ -414,7 +473,7 @@
mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false;
}
- String vcard;
+ final String vcard;
try {
if (mIdColumn >= 0) {
vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
@@ -437,8 +496,7 @@
mCursor.moveToNext();
}
- // This function does not care the OutOfMemoryError on the handler side
- // :-P
+ // This function does not care the OutOfMemoryError on the handler side :-P
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -457,7 +515,7 @@
}
private String createOneEntryInternal(final String contactId,
- Method getEntityIteratorMethod) throws VCardException {
+ final Method getEntityIteratorMethod) throws VCardException {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
// The resolver may return the entity iterator with no data. It is possible.
@@ -527,20 +585,34 @@
}
}
- final VCardBuilder builder = new VCardBuilder(mVCardType);
- builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
- .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
- .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
- .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
- .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
- .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
- .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
- .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
- .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
- .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
- .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
- .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
- return builder.toString();
+ return buildVCard(contentValuesListMap);
+ }
+
+ /**
+ * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
+ * {ContactsContract}. Developers can override this method to customize the output.
+ */
+ public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
+ if (contentValuesListMap == null) {
+ Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
+ return "";
+ } else {
+ final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
+ // TODO: Android-specific X attributes?
+ builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ return builder.toString();
+ }
}
public void terminate() {
@@ -563,26 +635,38 @@
@Override
public void finalize() {
if (!mTerminateIsCalled) {
+ Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
terminate();
}
}
+ /**
+ * @return returns the number of available entities. The return value is undefined
+ * when this object is not ready yet (typically when {{@link #init()} is not called
+ * or when {@link #terminate()} is already called).
+ */
public int getCount() {
if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
return 0;
}
return mCursor.getCount();
}
+ /**
+ * @return true when there's no entity to be built. The return value is undefined
+ * when this object is not ready yet.
+ */
public boolean isAfterLast() {
if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
return false;
}
return mCursor.isAfterLast();
}
/**
- * @return Return the error reason if possible.
+ * @return Returns the error reason.
*/
public String getErrorReason() {
return mErrorReason;
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 3442ae7..01edf93 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,6 +15,7 @@
*/
package android.pim.vcard;
+import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
@@ -43,10 +44,28 @@
/* package */ static final int PARSE_TYPE_FOMA = 3; // For Japanese FOMA mobile phones.
/* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4;
- // Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and
- // decode the unicode to the original charset. If not, this setting will cause some bug.
- public static final String DEFAULT_CHARSET = "iso-8859-1";
-
+ /**
+ * <P>
+ * The charset used during import.
+ * </P>
+ * <P>
+ * We cannot determine which charset should be used to interpret a given vCard file,
+ * while we have to decode sime encoded data (e.g. BASE64) to binary.
+ * In order to avoid "misinterpretation" of charset as much as possible,
+ * "ISO-8859-1" (a.k.a Latin-1) is first used for reading a stream.
+ * When charset is specified in a property (with "CHARSET=..." parameter),
+ * the string is decoded to raw bytes and encoded into the specific charset,
+ * assuming "ISO-8859-1" is able to map "all" 8bit characters to some unicode,
+ * and it has 1 to 1 mapping in all 8bit characters.
+ * If the assumption is not correct, this setting will cause some bug.
+ * </P>
+ */
+ /* package */ static final String DEFAULT_TEMPORARY_CHARSET = "ISO-8859-1";
+
+ // TODO: still intermediate procedures uses this charset. Fix it.
+ public static final String DEFAULT_IMPORT_CHARSET = "ISO-8859-1";
+ public static final String DEFAULT_EXPORT_CHARSET = "UTF-8";
+
public static final int FLAG_V21 = 0;
public static final int FLAG_V30 = 1;
@@ -58,10 +77,13 @@
private static final int NAME_ORDER_MASK = 0xC;
// 0x10 is reserved for safety
-
- private static final int FLAG_CHARSET_UTF8 = 0;
- private static final int FLAG_CHARSET_SHIFT_JIS = 0x100;
- private static final int FLAG_CHARSET_MASK = 0xF00;
+
+ /*
+ * These flags are ignored when charset is explicitly given by a caller.
+ */
+ private static final int FLAG_USE_UTF8_FOR_EXPORT = 0;
+ private static final int FLAG_USE_SHIFT_JIS_FOR_EXPORT = 0x100;
+ private static final int FLAG_CHARSET_MASK_FOR_EKPORT = 0xF00;
/**
* The flag indicating the vCard composer will add some "X-" properties used only in Android
@@ -182,6 +204,30 @@
*/
public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
+ /**
+ * <P>
+ * The flag indicating the vCard composer does touch nothing toward phone number Strings
+ * but leave it as is.
+ * </P>
+ * <P>
+ * The vCard specifications mention nothing toward phone numbers, while some devices
+ * do (wrongly, but with innevitable reasons).
+ * For example, there's a possibility Japanese mobile phones are expected to have
+ * just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones
+ * should get such characters. To make exported vCard simple for external parsers,
+ * we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and
+ * removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)"
+ * becomes "111-222-3333").
+ * Unfortunate side effect of that use was some control characters used in the other
+ * areas may be badly affected by the formatting.
+ * </P>
+ * <P>
+ * This flag disables that formatting, affecting both importer and exporter.
+ * If the user is aware of some side effects due to the implicit formatting, use this flag.
+ * </P>
+ */
+ public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
+
//// The followings are VCard types available from importer/exporter. ////
/**
@@ -196,7 +242,7 @@
* </P>
*/
public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
- (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
+ (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_USE_UTF8_FOR_EXPORT |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
@@ -210,7 +256,7 @@
* </P>
*/
public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
- (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
+ (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_USE_UTF8_FOR_EXPORT |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
@@ -222,7 +268,7 @@
* </P>
*/
public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
- (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
+ (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_USE_UTF8_FOR_EXPORT |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
@@ -236,7 +282,7 @@
* </P>
*/
public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
- (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
+ (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_USE_UTF8_FOR_EXPORT |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
@@ -250,7 +296,7 @@
* </P>
*/
public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_UTF8_FOR_EXPORT |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
@@ -265,7 +311,7 @@
* </P>
*/
public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_SHIFT_JIS_FOR_EXPORT |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
@@ -280,7 +326,7 @@
* </P>
*/
public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
- (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_USE_SHIFT_JIS_FOR_EXPORT |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
@@ -294,7 +340,7 @@
* </P>
*/
public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
- (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
+ (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_USE_UTF8_FOR_EXPORT |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
@@ -310,7 +356,7 @@
* </P>
*/
public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_SHIFT_JIS_FOR_EXPORT |
FLAG_CONVERT_PHONETIC_NAME_STRINGS |
FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
@@ -379,12 +425,17 @@
return !isV30(vcardType);
}
- public static boolean usesUtf8(final int vcardType) {
- return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8);
+ /* package */ static boolean shouldUseUtf8ForExport(final int vcardType) {
+ return ((vcardType & FLAG_CHARSET_MASK_FOR_EKPORT) == FLAG_USE_UTF8_FOR_EXPORT);
}
- public static boolean usesShiftJis(final int vcardType) {
- return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS);
+ /**
+ * Shift_JIS (a charset for Japanese text files) needs special handling to select
+ * carrer specific variants.
+ * @hide just for test
+ */
+ public static boolean shouldUseShiftJisForExport(final int vcardType) {
+ return ((vcardType & FLAG_CHARSET_MASK_FOR_EKPORT) == FLAG_USE_SHIFT_JIS_FOR_EXPORT);
}
public static int getNameOrderType(final int vcardType) {
@@ -423,6 +474,10 @@
return sJapaneseMobileTypeSet.contains(vcardType);
}
+ /* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) {
+ return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0);
+ }
+
public static boolean needsToConvertPhoneticString(final int vcardType) {
return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
}
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
index 8c07126..e11b1fd 100644
--- a/core/java/android/pim/vcard/VCardConstants.java
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -109,6 +109,12 @@
public static final String PARAM_TYPE_BBS = "BBS";
public static final String PARAM_TYPE_VIDEO = "VIDEO";
+ public static final String PARAM_ENCODING_7BIT = "7BIT";
+ public static final String PARAM_ENCODING_8BIT = "8BIT";
+ public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
+ public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1
+ public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0
+
// TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
// These types are basically encoded to "X-" parameters when composing vCard.
// Parser passes these when "X-" is added to the parameter or not.
@@ -130,10 +136,6 @@
// Do not use in composer side.
public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
- // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
- // vCard 3.0.
- public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
-
public interface ImportOnly {
public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
// Some device emits this "X-" parameter for expressing Google Talk,
@@ -142,6 +144,12 @@
public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
}
+ //// Mainly for package constants.
+
+ // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
+ // SORT-STRING invCard 3.0.
+ /* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
+
/* package */ static final int MAX_DATA_COLUMN = 15;
/* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
index 1327770..7c7e9b8 100644
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -488,7 +488,7 @@
final StringBuilder builder = new StringBuilder();
final String trimed = data.trim();
final String formattedNumber;
- if (type == Phone.TYPE_PAGER) {
+ if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
formattedNumber = trimed;
} else {
final int length = trimed.length();
@@ -500,8 +500,7 @@
}
// Use NANP in default when there's no information about locale.
- final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ?
- PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
+ final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
}
PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
index 290ca2b..4efb105 100644
--- a/core/java/android/pim/vcard/VCardEntryConstructor.java
+++ b/core/java/android/pim/vcard/VCardEntryConstructor.java
@@ -80,7 +80,7 @@
if (inputCharset != null) {
mInputCharset = inputCharset;
} else {
- mInputCharset = VCardConfig.DEFAULT_CHARSET;
+ mInputCharset = VCardConfig.DEFAULT_TEMPORARY_CHARSET;
}
if (charsetForDetodedBytes != null) {
mCharsetForDecodedBytes = charsetForDetodedBytes;
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index 57c52a6..71ab5c4 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -20,82 +20,60 @@
import java.io.IOException;
import java.io.InputStream;
-public abstract class VCardParser {
- protected final int mParseType;
- protected boolean mCanceled;
-
- public VCardParser() {
- this(VCardConfig.PARSE_TYPE_UNKNOWN);
- }
-
- public VCardParser(int parseType) {
- mParseType = parseType;
- }
-
+public interface VCardParser {
/**
- * <P>
- * Parses the given stream and send the VCard data into VCardBuilderBase object.
- * </P.
- * <P>
+ * <p>
+ * Parses the given stream and send the vCard data into VCardBuilderBase object.
+ * </p>.
+ * <p>
* Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
* local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
- * formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1,
- * In some exreme case, some VCard may have different charsets in one VCard (though
- * we do not see any device which emits such kind of malicious data)
- * </P>
- * <P>
- * In order to avoid "misunderstanding" charset as much as possible, this method
- * use "ISO-8859-1" for reading the stream. When charset is specified in some property
- * (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to
- * the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit
- * characters, which is not completely sure. In some cases, this "decoding-encoding"
- * scheme may fail. To avoid the case,
- * </P>
- * <P>
- * We recommend you to use {@link VCardSourceDetector} and detect which kind of source the
- * VCard comes from and explicitly specify a charset using the result.
- * </P>
+ * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
+ * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
+ * </p>
+ * <p>
+ * We recommend you use {@link VCardSourceDetector} and detect which kind of source the
+ * vCard comes from and explicitly specify a charset using the result.
+ * </p>
*
* @param is The source to parse.
* @param interepreter A {@link VCardInterpreter} object which used to construct data.
* @return Returns true for success. Otherwise returns false.
* @throws IOException, VCardException
*/
- public abstract boolean parse(InputStream is, VCardInterpreter interepreter)
+ public boolean parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException;
-
+
/**
- * <P>
+ * <p>
* The method variants which accept charset.
- * </P>
- * <P>
- * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
- * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
- * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
- * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K).
- * </P>
+ * </p>
*
* @param is The source to parse.
* @param charset Charset to be used.
- * @param builder The VCardBuilderBase object.
+ * @param interpreter The VCardBuilderBase object.
* @return Returns true when successful. Otherwise returns false.
* @throws IOException, VCardException
*/
- public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder)
+ public boolean parse(InputStream is, String charset, VCardInterpreter interpreter)
throws IOException, VCardException;
/**
* The method variants which tells this object the operation is already canceled.
+ * @hide
*/
- public abstract void parse(InputStream is, String charset,
+ // TODO: remove this if possible.
+ public boolean parse(InputStream is, String charset,
VCardInterpreter builder, boolean canceled)
throws IOException, VCardException;
-
+
/**
- * Cancel parsing.
- * Actual cancel is done after the end of the current one vcard entry parsing.
+ * <p>
+ * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
+ * </p>
+ * <p>
+ * Actual cancel is done after parsing the current vcard.
+ * </p>
*/
- public void cancel() {
- mCanceled = true;
- }
+ public abstract void cancel();
}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V21.java b/core/java/android/pim/vcard/VCardParserImpl_V21.java
new file mode 100644
index 0000000..22fdb75
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V21.java
@@ -0,0 +1,991 @@
+/*
+ * Copyright (C) 2010 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 android.pim.vcard;
+
+import android.pim.vcard.exception.VCardAgentNotSupportedException;
+import android.pim.vcard.exception.VCardException;
+import android.pim.vcard.exception.VCardInvalidCommentLineException;
+import android.pim.vcard.exception.VCardInvalidLineException;
+import android.pim.vcard.exception.VCardNestedException;
+import android.pim.vcard.exception.VCardVersionException;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard parsing. Based on vCard 2.1,
+ * </p>
+ */
+/* package */ class VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V21";
+
+ private static final class CustomBufferedReader extends BufferedReader {
+ private long mTime;
+
+ public CustomBufferedReader(Reader in) {
+ super(in);
+ }
+
+ @Override
+ public String readLine() throws IOException {
+ long start = System.currentTimeMillis();
+ String ret = super.readLine();
+ long end = System.currentTimeMillis();
+ mTime += end - start;
+ return ret;
+ }
+
+ public long getTotalmillisecond() {
+ return mTime;
+ }
+ }
+
+ private static final String sDefaultEncoding = "8BIT";
+
+ protected boolean mCanceled;
+ protected VCardInterpreter mInterpreter;
+
+ /**
+ * <p>
+ * The encoding type for deconding byte streams. This member variable is
+ * reset to a default encoding every time when a new item comes.
+ * </p>
+ * <p>
+ * "Encoding" in vCard is different from "Charset". It is mainly used for
+ * addresses, notes, images. "7BIT", "8BIT", "BASE64", and
+ * "QUOTED-PRINTABLE" are known examples.
+ * </p>
+ */
+ protected String mCurrentEncoding;
+
+ /**
+ * <p>
+ * The reader object to be used internally.
+ * </p>
+ * <p>
+ * Developers should not directly read a line from this object. Use
+ * getLine() unless there some reason.
+ * </p>
+ */
+ protected BufferedReader mReader;
+
+ /**
+ * <p>
+ * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
+ * specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownTypeSet = new HashSet<String>();
+
+ /**
+ * <p>
+ * Set for storing unkonwn VALUE attributes, which is not acceptable in
+ * vCard specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownValueSet = new HashSet<String>();
+
+
+ // In some cases, vCard is nested. Currently, we only consider the most
+ // interior vCard data.
+ // See v21_foma_1.vcf in test directory for more information.
+ private int mNestCount;
+
+ // Used only for parsing END:VCARD.
+ private String mPreviousLine;
+
+ // For measuring performance.
+ private long mTimeTotal;
+ private long mTimeReadStartRecord;
+ private long mTimeReadEndRecord;
+ private long mTimeStartProperty;
+ private long mTimeEndProperty;
+ private long mTimeParseItems;
+ private long mTimeParseLineAndHandleGroup;
+ private long mTimeParsePropertyValues;
+ private long mTimeParseAdrOrgN;
+ private long mTimeHandleMiscPropertyValue;
+ private long mTimeHandleQuotedPrintable;
+ private long mTimeHandleBase64;
+
+ /**
+ * <p>
+ * Same as {{@link #VCardParserImpl_V21(VCardSourceDetector)} with
+ * {@link VCardConfig#PARSE_TYPE_UNKNOWN}.
+ * </p>
+ */
+ public VCardParserImpl_V21() {
+ this(VCardConfig.PARSE_TYPE_UNKNOWN);
+ }
+
+ /**
+ * <p>
+ * The constructor which uses the estimated type available from a given detector.
+ * </p>
+ */
+ public VCardParserImpl_V21(VCardSourceDetector detector) {
+ this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
+ }
+
+ /**
+ * <p>
+ * The constructor which uses a given parse type like
+ * {@link VCardConfig#PARSE_TYPE_UNKNOWN}.
+ * </p>
+ * <p>
+ * This should be used only when you already know the exact type to be used.
+ * </p>
+ */
+ public VCardParserImpl_V21(int parseType) {
+ if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
+ mNestCount = 1;
+ }
+ }
+
+ /**
+ * <p>
+ * Parses the file at the given position.
+ * </p>
+ */
+ // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
+ protected void parseVCardFile() throws IOException, VCardException {
+ boolean firstRead = true;
+ while (true) {
+ if (mCanceled) {
+ break;
+ }
+ if (!parseOneVCard(firstRead)) {
+ break;
+ }
+ firstRead = false;
+ }
+
+ if (mNestCount > 0) {
+ boolean useCache = true;
+ for (int i = 0; i < mNestCount; i++) {
+ readEndVCard(useCache, true);
+ useCache = false;
+ }
+ }
+ }
+
+ /**
+ * @return true when a given property name is a valid property name.
+ */
+ protected boolean isValidPropertyName(final String propertyName) {
+ if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
+ propertyName.startsWith("X-"))
+ && !mUnknownTypeSet.contains(propertyName)) {
+ mUnknownTypeSet.add(propertyName);
+ Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
+ }
+ return true;
+ }
+
+ /**
+ * @return String. It may be null, or its length may be 0
+ * @throws IOException
+ */
+ protected String getLine() throws IOException {
+ return mReader.readLine();
+ }
+
+ /**
+ * @return String with it's length > 0
+ * @throws IOException
+ * @throws VCardException when the stream reached end of line
+ */
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.trim().length() > 0) {
+ return line;
+ }
+ }
+ }
+
+ /*
+ * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+ * items *CRLF
+ * "END" [ws] ":" [ws] "VCARD"
+ */
+ private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException {
+ boolean allowGarbage = false;
+ if (firstRead) {
+ if (mNestCount > 0) {
+ for (int i = 0; i < mNestCount; i++) {
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ allowGarbage = true;
+ }
+ }
+ }
+
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ long start;
+ if (mInterpreter != null) {
+ start = System.currentTimeMillis();
+ mInterpreter.startEntry();
+ mTimeReadStartRecord += System.currentTimeMillis() - start;
+ }
+ start = System.currentTimeMillis();
+ parseItems();
+ mTimeParseItems += System.currentTimeMillis() - start;
+ readEndVCard(true, false);
+ if (mInterpreter != null) {
+ start = System.currentTimeMillis();
+ mInterpreter.endEntry();
+ mTimeReadEndRecord += System.currentTimeMillis() - start;
+ }
+ return true;
+ }
+
+ /**
+ * @return True when successful. False when reaching the end of line
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+ String line;
+ do {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ return false;
+ } else if (line.trim().length() > 0) {
+ break;
+ }
+ }
+ String[] strArray = line.split(":", 2);
+ int length = strArray.length;
+
+ // Though vCard 2.1/3.0 specification does not allow lower cases,
+ // vCard file emitted by some external vCard expoter have such
+ // invalid Strings.
+ // So we allow it.
+ // e.g. BEGIN:vCard
+ if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN")
+ && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+ return true;
+ } else if (!allowGarbage) {
+ if (mNestCount > 0) {
+ mPreviousLine = line;
+ return false;
+ } else {
+ throw new VCardException("Expected String \"BEGIN:VCARD\" did not come "
+ + "(Instead, \"" + line + "\" came)");
+ }
+ }
+ } while (allowGarbage);
+
+ throw new VCardException("Reached where must not be reached.");
+ }
+
+ /**
+ * <p>
+ * The arguments useCache and allowGarbase are usually true and false
+ * accordingly when this function is called outside this function itself.
+ * </p>
+ *
+ * @param useCache When true, line is obtained from mPreviousline.
+ * Otherwise, getLine() is used.
+ * @param allowGarbage When true, ignore non "END:VCARD" line.
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException,
+ VCardException {
+ String line;
+ do {
+ if (useCache) {
+ // Though vCard specification does not allow lower cases,
+ // some data may have them, so we allow it.
+ line = mPreviousLine;
+ } else {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Expected END:VCARD was not found.");
+ } else if (line.trim().length() > 0) {
+ break;
+ }
+ }
+ }
+
+ String[] strArray = line.split(":", 2);
+ if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END")
+ && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+ return;
+ } else if (!allowGarbage) {
+ throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
+ }
+ useCache = false;
+ } while (allowGarbage);
+ }
+
+ /*
+ * items = *CRLF item / item
+ */
+ protected void parseItems() throws IOException, VCardException {
+ boolean ended = false;
+
+ if (mInterpreter != null) {
+ long start = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - start;
+ }
+ ended = parseItem();
+ if (mInterpreter != null && !ended) {
+ long start = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - start;
+ }
+
+ while (!ended) {
+ // follow VCARD ,it wont reach endProperty
+ if (mInterpreter != null) {
+ long start = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - start;
+ }
+ try {
+ ended = parseItem();
+ } catch (VCardInvalidCommentLineException e) {
+ Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
+ ended = false;
+ }
+ if (mInterpreter != null && !ended) {
+ long start = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - start;
+ }
+ }
+ }
+
+ /*
+ * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR"
+ * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts
+ * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."]
+ * "AGENT" [params] ":" vcard CRLF
+ */
+ protected boolean parseItem() throws IOException, VCardException {
+ mCurrentEncoding = sDefaultEncoding;
+
+ final String line = getNonEmptyLine();
+ long start = System.currentTimeMillis();
+
+ String[] propertyNameAndValue = separateLineAndHandleGroup(line);
+ if (propertyNameAndValue == null) {
+ return true;
+ }
+ if (propertyNameAndValue.length != 2) {
+ throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
+ }
+ String propertyName = propertyNameAndValue[0].toUpperCase();
+ String propertyValue = propertyNameAndValue[1];
+
+ mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
+
+ if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) {
+ start = System.currentTimeMillis();
+ handleMultiplePropertyValue(propertyName, propertyValue);
+ mTimeParseAdrOrgN += System.currentTimeMillis() - start;
+ return false;
+ } else if (propertyName.equals("AGENT")) {
+ handleAgent(propertyValue);
+ return false;
+ } else if (isValidPropertyName(propertyName)) {
+ if (propertyName.equals("BEGIN")) {
+ if (propertyValue.equals("VCARD")) {
+ throw new VCardNestedException("This vCard has nested vCard data in it.");
+ } else {
+ throw new VCardException("Unknown BEGIN type: " + propertyValue);
+ }
+ } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) {
+ throw new VCardVersionException("Incompatible version: " + propertyValue + " != "
+ + getVersionString());
+ }
+ start = System.currentTimeMillis();
+ handlePropertyValue(propertyName, propertyValue);
+ mTimeParsePropertyValues += System.currentTimeMillis() - start;
+ return false;
+ }
+
+ throw new VCardException("Unknown property name: \"" + propertyName + "\"");
+ }
+
+ // For performance reason, the states for group and property name are merged into one.
+ static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
+ static private final int STATE_PARAMS = 1;
+ // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
+ static private final int STATE_PARAMS_IN_DQUOTE = 2;
+
+ protected String[] separateLineAndHandleGroup(String line) throws VCardException {
+ final String[] propertyNameAndValue = new String[2];
+ final int length = line.length();
+ if (length > 0 && line.charAt(0) == '#') {
+ throw new VCardInvalidCommentLineException();
+ }
+
+ int state = STATE_GROUP_OR_PROPERTY_NAME;
+ int nameIndex = 0;
+
+ // This loop is developed so that we don't have to take care of bottle neck here.
+ // Refactor carefully when you need to do so.
+ for (int i = 0; i < length; i++) {
+ final char ch = line.charAt(i);
+ switch (state) {
+ case STATE_GROUP_OR_PROPERTY_NAME: {
+ if (ch == ':') { // End of a property name.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyName(propertyName);
+ }
+ propertyNameAndValue[0] = propertyName;
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ } else if (ch == '.') { // Each group is followed by the dot.
+ final String groupName = line.substring(nameIndex, i);
+ if (groupName.length() == 0) {
+ Log.w(LOG_TAG, "Empty group found. Ignoring.");
+ } else if (mInterpreter != null) {
+ mInterpreter.propertyGroup(groupName);
+ }
+ nameIndex = i + 1; // Next should be another group or a property name.
+ } else if (ch == ';') { // End of property name and beginneng of parameters.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyName(propertyName);
+ }
+ propertyNameAndValue[0] = propertyName;
+ nameIndex = i + 1;
+ state = STATE_PARAMS; // Start parameter parsing.
+ }
+ break;
+ }
+ case STATE_PARAMS: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS_IN_DQUOTE;
+ } else if (ch == ';') { // Starts another param.
+ handleParams(line.substring(nameIndex, i));
+ nameIndex = i + 1;
+ } else if (ch == ':') { // End of param and beginenning of values.
+ handleParams(line.substring(nameIndex, i));
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ }
+ break;
+ }
+ case STATE_PARAMS_IN_DQUOTE: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS;
+ }
+ break;
+ }
+ }
+ }
+
+ throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
+ }
+
+ /*
+ * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param /
+ * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws]
+ * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "="
+ * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "="
+ * [ws] word / knowntype
+ */
+ protected void handleParams(String params) throws VCardException {
+ final String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ final String paramName = strArray[0].trim().toUpperCase();
+ String paramValue = strArray[1].trim();
+ if (paramName.equals("TYPE")) {
+ handleType(paramValue);
+ } else if (paramName.equals("VALUE")) {
+ handleValue(paramValue);
+ } else if (paramName.equals("ENCODING")) {
+ handleEncoding(paramValue);
+ } else if (paramName.equals("CHARSET")) {
+ handleCharset(paramValue);
+ } else if (paramName.equals("LANGUAGE")) {
+ handleLanguage(paramValue);
+ } else if (paramName.startsWith("X-")) {
+ handleAnyParam(paramName, paramValue);
+ } else {
+ throw new VCardException("Unknown type \"" + paramName + "\"");
+ }
+ } else {
+ handleParamWithoutName(strArray[0]);
+ }
+ }
+
+ /**
+ * vCard 3.0 parser implementation may throw VCardException.
+ */
+ @SuppressWarnings("unused")
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ handleType(paramValue);
+ }
+
+ /*
+ * ptypeval = knowntype / "X-" word
+ */
+ protected void handleType(final String ptypeval) {
+ if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
+ || ptypeval.startsWith("X-"))
+ && !mUnknownTypeSet.contains(ptypeval)) {
+ mUnknownTypeSet.add(ptypeval);
+ Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("TYPE");
+ mInterpreter.propertyParamValue(ptypeval);
+ }
+ }
+
+ /*
+ * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+ */
+ protected void handleValue(final String pvalueval) {
+ if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
+ || pvalueval.startsWith("X-")
+ || mUnknownValueSet.contains(pvalueval))) {
+ mUnknownValueSet.add(pvalueval);
+ Log.w(LOG_TAG, String.format(
+ "The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("VALUE");
+ mInterpreter.propertyParamValue(pvalueval);
+ }
+ }
+
+ /*
+ * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
+ */
+ protected void handleEncoding(String pencodingval) throws VCardException {
+ if (getAvailableEncodingSet().contains(pencodingval) ||
+ pencodingval.startsWith("X-")) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("ENCODING");
+ mInterpreter.propertyParamValue(pencodingval);
+ }
+ mCurrentEncoding = pencodingval;
+ } else {
+ throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
+ }
+ }
+
+ /**
+ * <p>
+ * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+ * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
+ * We allow any charset.
+ * </p>
+ */
+ protected void handleCharset(String charsetval) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("CHARSET");
+ mInterpreter.propertyParamValue(charsetval);
+ }
+ }
+
+ /**
+ * See also Section 7.1 of RFC 1521
+ */
+ protected void handleLanguage(String langval) throws VCardException {
+ String[] strArray = langval.split("-");
+ if (strArray.length != 2) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ String tmp = strArray[0];
+ int length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ tmp = strArray[1];
+ length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("LANGUAGE");
+ mInterpreter.propertyParamValue(langval);
+ }
+ }
+
+ private boolean isAsciiLetter(char ch) {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Mainly for "X-" type. This accepts any kind of type without check.
+ */
+ protected void handleAnyParam(String paramName, String paramValue) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType(paramName);
+ mInterpreter.propertyParamValue(paramValue);
+ }
+ }
+
+ protected void handlePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ final String upperEncoding = mCurrentEncoding.toUpperCase();
+ if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
+ final long start = System.currentTimeMillis();
+ final String result = getQuotedPrintable(propertyValue);
+ if (mInterpreter != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mInterpreter.propertyValues(v);
+ }
+ mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
+ } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
+ || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
+ final long start = System.currentTimeMillis();
+ // It is very rare, but some BASE64 data may be so big that
+ // OutOfMemoryError occurs. To ignore such cases, use try-catch.
+ try {
+ final String result = getBase64(propertyValue);
+ if (mInterpreter != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mInterpreter.propertyValues(v);
+ }
+ } catch (OutOfMemoryError error) {
+ Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
+ if (mInterpreter != null) {
+ mInterpreter.propertyValues(null);
+ }
+ }
+ mTimeHandleBase64 += System.currentTimeMillis() - start;
+ } else {
+ if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
+ upperEncoding.startsWith("X-"))) {
+ Log.w(LOG_TAG,
+ String.format("The encoding \"%s\" is unsupported by vCard %s",
+ mCurrentEncoding, getVersionString()));
+ }
+
+ final long start = System.currentTimeMillis();
+ if (mInterpreter != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(maybeUnescapeText(propertyValue));
+ mInterpreter.propertyValues(v);
+ }
+ mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
+ }
+ }
+
+ /**
+ * <p>
+ * Parses and returns Quoted-Printable.
+ * </p>
+ *
+ * @param firstString The string following a parameter name and attributes.
+ * Example: "string" in
+ * "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
+ * @return whole Quoted-Printable string, including a given argument and
+ * following lines. Excludes the last empty line following to Quoted
+ * Printable lines.
+ * @throws IOException
+ * @throws VCardException
+ */
+ private String getQuotedPrintable(String firstString) throws IOException, VCardException {
+ // Specifically, there may be some padding between = and CRLF.
+ // See the following:
+ //
+ // qp-line := *(qp-segment transport-padding CRLF)
+ // qp-part transport-padding
+ // qp-segment := qp-section *(SPACE / TAB) "="
+ // ; Maximum length of 76 characters
+ //
+ // e.g. (from RFC 2045)
+ // Now's the time =
+ // for all folk to come=
+ // to the aid of their country.
+ if (firstString.trim().endsWith("=")) {
+ // remove "transport-padding"
+ int pos = firstString.length() - 1;
+ while (firstString.charAt(pos) != '=') {
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString.substring(0, pos + 1));
+ builder.append("\r\n");
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing a Quoted-Printable String");
+ }
+ if (line.trim().endsWith("=")) {
+ // remove "transport-padding"
+ pos = line.length() - 1;
+ while (line.charAt(pos) != '=') {
+ }
+ builder.append(line.substring(0, pos + 1));
+ builder.append("\r\n");
+ } else {
+ builder.append(line);
+ break;
+ }
+ }
+ return builder.toString();
+ } else {
+ return firstString;
+ }
+ }
+
+ protected String getBase64(String firstString) throws IOException, VCardException {
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ String line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * <p>
+ * Mainly for "ADR", "ORG", and "N"
+ * </p>
+ */
+ /*
+ * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr,
+ * Street, Locality, Region, Postal Code, Country Name orgparts =
+ * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are
+ * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family,
+ * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III,
+ * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a
+ * semicolon in this string, it must be escaped ; with a "\" character. We
+ * do not care the number of "strnosemi" here. We are not sure whether we
+ * should add "\" CRLF to each value. We exclude them for now.
+ */
+ protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some
+ // softwares/devices
+ // emit such data.
+ if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ propertyValue = getQuotedPrintable(propertyValue);
+ }
+
+ if (mInterpreter != null) {
+ mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue,
+ (getVersion() == VCardConfig.FLAG_V30)));
+ }
+ }
+
+ /*
+ * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
+ * error toward the AGENT property.
+ * // TODO: Support AGENT property.
+ * item =
+ * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws]
+ * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD"
+ */
+ protected void handleAgent(final String propertyValue) throws VCardException {
+ if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
+ // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
+ return;
+ } else {
+ throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
+ }
+ }
+
+ /**
+ * For vCard 3.0.
+ */
+ protected String maybeUnescapeText(final String text) {
+ return text;
+ }
+
+ /**
+ * Returns unescaped String if the character should be unescaped. Return
+ * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";"
+ * while "\x" should not be.
+ */
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ /* package */ static String unescapeCharacter(final char ch) {
+ // Original vCard 2.1 specification does not allow transformation
+ // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous
+ // implementation of
+ // this class allowed them, so keep it as is.
+ if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
+ return String.valueOf(ch);
+ } else {
+ return null;
+ }
+ }
+
+ private void showPerformanceInfo() {
+ Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
+ if (mReader instanceof CustomBufferedReader) {
+ Log.d(LOG_TAG, "Total readLine time: "
+ + ((CustomBufferedReader) mReader).getTotalmillisecond() + " ms");
+ }
+ Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord
+ + " ms");
+ Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms");
+ Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup
+ + " ms");
+ Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
+ Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
+ Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue
+ + " ms");
+ Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms");
+ Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
+ }
+
+ /**
+ * @return {@link VCardConfig#FLAG_V21}
+ */
+ protected int getVersion() {
+ return VCardConfig.FLAG_V21;
+ }
+
+ /**
+ * @return {@link VCardConfig#FLAG_V30}
+ */
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V21;
+ }
+
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V21.sKnownPropertyNameSet;
+ }
+
+ protected Set<String> getKnownTypeSet() {
+ return VCardParser_V21.sKnownTypeSet;
+ }
+
+ protected Set<String> getKnownValueSet() {
+ return VCardParser_V21.sKnownValueSet;
+ }
+
+ protected Set<String> getAvailableEncodingSet() {
+ return VCardParser_V21.sAvailableEncoding;
+ }
+
+ protected String getDefaultEncoding() {
+ return sDefaultEncoding;
+ }
+
+
+ public boolean parse(InputStream is, String inputCharset, VCardInterpreter interpreter)
+ throws IOException, VCardException {
+ if (is == null) {
+ throw new NullPointerException("InputStream must not be null.");
+ }
+ if (inputCharset == null) {
+ inputCharset = VCardConfig.DEFAULT_TEMPORARY_CHARSET;
+ }
+
+ final InputStreamReader tmpReader = new InputStreamReader(is, inputCharset);
+ if (VCardConfig.showPerformanceLog()) {
+ mReader = new CustomBufferedReader(tmpReader);
+ } else {
+ mReader = new BufferedReader(tmpReader);
+ }
+
+ mInterpreter = interpreter;
+
+ long start = System.currentTimeMillis();
+ if (mInterpreter != null) {
+ mInterpreter.start();
+ }
+ parseVCardFile();
+ if (mInterpreter != null) {
+ mInterpreter.end();
+ }
+ mTimeTotal += System.currentTimeMillis() - start;
+
+ if (VCardConfig.showPerformanceLog()) {
+ showPerformanceInfo();
+ }
+
+ return true;
+ }
+
+ public boolean parse(InputStream is, String charset,
+ VCardInterpreter builder, boolean canceled)
+ throws IOException, VCardException {
+ mCanceled = canceled;
+ return parse(is, charset, builder);
+ }
+
+ public final void cancel() {
+ mCanceled = true;
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V30.java b/core/java/android/pim/vcard/VCardParserImpl_V30.java
new file mode 100644
index 0000000..fb6ee4f
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V30.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2010 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 android.pim.vcard;
+
+import java.io.IOException;
+import java.util.Set;
+
+import android.pim.vcard.exception.VCardException;
+import android.util.Log;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard 3.0 parsing.
+ * </p>
+ * <p>
+ * This class inherits vCard 2.1 implementation since technically they are similar,
+ * while specifically there's logical no relevance between them.
+ * So that developers are not confused with the inheritance,
+ * {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
+ * {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
+ * </p>
+ */
+/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V30";
+
+ private String mPreviousLine;
+ private boolean mEmittedAgentWarning = false;
+
+ public VCardParserImpl_V30() {
+ super();
+ }
+
+ public VCardParserImpl_V30(VCardSourceDetector detector) {
+ this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
+ }
+
+ public VCardParserImpl_V30(int parseMode) {
+ super(parseMode);
+ }
+
+ @Override
+ protected int getVersion() {
+ return VCardConfig.FLAG_V30;
+ }
+
+ @Override
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V30;
+ }
+
+ @Override
+ protected String getLine() throws IOException {
+ if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ } else {
+ return mReader.readLine();
+ }
+ }
+
+ /**
+ * vCard 3.0 requires that the line with space at the beginning of the line
+ * must be combined with previous line.
+ */
+ @Override
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ StringBuilder builder = null;
+ while (true) {
+ line = mReader.readLine();
+ if (line == null) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.length() == 0) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
+ if (builder != null) {
+ // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
+ // Following is the excerpts from it.
+ //
+ // DESCRIPTION:This is a long description that exists on a long line.
+ //
+ // Can be represented as:
+ //
+ // DESCRIPTION:This is a long description
+ // that exists on a long line.
+ //
+ // It could also be represented as:
+ //
+ // DESCRIPTION:This is a long descrip
+ // tion that exists o
+ // n a long line.
+ builder.append(line.substring(1));
+ } else if (mPreviousLine != null) {
+ builder = new StringBuilder();
+ builder.append(mPreviousLine);
+ mPreviousLine = null;
+ builder.append(line.substring(1));
+ } else {
+ throw new VCardException("Space exists at the beginning of the line");
+ }
+ } else {
+ if (mPreviousLine == null) {
+ mPreviousLine = line;
+ if (builder != null) {
+ return builder.toString();
+ }
+ } else {
+ String ret = mPreviousLine;
+ mPreviousLine = line;
+ return ret;
+ }
+ }
+ }
+ }
+
+ /*
+ * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
+ * 1 * (contentline)
+ * ;A vCard object MUST include the VERSION, FN and N types.
+ * [group "."] "END" ":" "VCARD" 1 * CRLF
+ */
+ @Override
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ return super.readBeginVCard(allowGarbage);
+ }
+
+ @Override
+ protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ super.readEndVCard(useCache, allowGarbage);
+ }
+
+ /**
+ * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
+ */
+ @Override
+ protected void handleParams(final String params) throws VCardException {
+ try {
+ super.handleParams(params);
+ } catch (VCardException e) {
+ // maybe IANA type
+ String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ handleAnyParam(strArray[0], strArray[1]);
+ } else {
+ // Must not come here in the current implementation.
+ throw new VCardException(
+ "Unknown params value: " + params);
+ }
+ }
+ }
+
+ @Override
+ protected void handleAnyParam(final String paramName, final String paramValue) {
+ super.handleAnyParam(paramName, paramValue);
+ }
+
+ @Override
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ super.handleParamWithoutName(paramValue);
+ }
+
+ /*
+ * vCard 3.0 defines
+ *
+ * param = param-name "=" param-value *("," param-value)
+ * param-name = iana-token / x-name
+ * param-value = ptext / quoted-string
+ * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
+ */
+ @Override
+ protected void handleType(final String ptypevalues) {
+ String[] ptypeArray = ptypevalues.split(",");
+ mInterpreter.propertyParamType("TYPE");
+ for (String value : ptypeArray) {
+ int length = value.length();
+ if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
+ mInterpreter.propertyParamValue(value.substring(1, value.length() - 1));
+ } else {
+ mInterpreter.propertyParamValue(value);
+ }
+ }
+ }
+
+ @Override
+ protected void handleAgent(final String propertyValue) {
+ // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
+ //
+ // e.g.
+ // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
+ // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
+ // ET:jfriday@host.com\nEND:VCARD\n
+ //
+ // TODO: fix this.
+ //
+ // issue:
+ // vCard 3.0 also allows this as an example.
+ //
+ // AGENT;VALUE=uri:
+ // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
+ //
+ // This is not vCard. Should we support this?
+ //
+ // Just ignore the line for now, since we cannot know how to handle it...
+ if (!mEmittedAgentWarning) {
+ Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
+ mEmittedAgentWarning = true;
+ }
+ }
+
+ /**
+ * vCard 3.0 does not require two CRLF at the last of BASE64 data.
+ * It only requires that data should be MIME-encoded.
+ */
+ @Override
+ protected String getBase64(final String firstString)
+ throws IOException, VCardException {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ final String line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
+ mPreviousLine = line;
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
+ * ; \\ encodes \, \n or \N encodes newline
+ * ; \; encodes ;, \, encodes ,
+ *
+ * Note: Apple escapes ':' into '\:' while does not escape '\'
+ */
+ @Override
+ protected String maybeUnescapeText(final String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(final String text) {
+ StringBuilder builder = new StringBuilder();
+ final int length = text.length();
+ for (int i = 0; i < length; i++) {
+ char ch = text.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ final char next_ch = text.charAt(++i);
+ if (next_ch == 'n' || next_ch == 'N') {
+ builder.append("\n");
+ } else {
+ builder.append(next_ch);
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+
+ @Override
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(final char ch) {
+ if (ch == 'n' || ch == 'N') {
+ return "\n";
+ } else {
+ return String.valueOf(ch);
+ }
+ }
+
+ @Override
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V30.sKnownPropertyNameSet;
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index fe8cfb0..f7e496a 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -15,922 +15,112 @@
*/
package android.pim.vcard;
-import android.pim.vcard.exception.VCardAgentNotSupportedException;
import android.pim.vcard.exception.VCardException;
-import android.pim.vcard.exception.VCardInvalidCommentLineException;
-import android.pim.vcard.exception.VCardInvalidLineException;
-import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardVersionException;
-import android.util.Log;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
- * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail.
+ * </p>
+ * vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
+ * </p>
+ * <p>
+ * The spec is written in 1996, and currently various types of "vCard 2.1" exist.
+ * To handle real the world vCard formats appropriately and effectively, this class does not
+ * obey with strict vCard 2.1.
+ * In stead, not only vCard spec but also real world vCard is considered.
+ * </p>
+ * e.g. A lot of devices and softwares let vCard importer/exporter to use
+ * the PNG format to determine the type of image, while it is not allowed in
+ * the original specification. As of 2010, we can see even the FLV format
+ * (possible in Japanese mobile phones).
+ * </p>
*/
-public class VCardParser_V21 extends VCardParser {
- private static final String LOG_TAG = "VCardParser_V21";
-
- /** Store the known-type */
- private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
- Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
- "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
- "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
- "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
- "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
- "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
- "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
- "WAVE", "AIFF", "PCM", "X509", "PGP"));
-
- /** Store the known-value */
- private static final HashSet<String> sKnownValueSet = new HashSet<String>(
- Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
-
- /** Store the property names available in vCard 2.1 */
- private static final HashSet<String> sAvailablePropertyNameSetV21 =
- new HashSet<String>(Arrays.asList(
- "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
- "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
- "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
+public final class VCardParser_V21 implements VCardParser {
+ /**
+ * A unmodifiable Set storing the property names available in the vCard 2.1 specification.
+ */
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+ "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+ "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
/**
+ * A unmodifiable Set storing the types known in vCard 2.1.
+ */
+ /* package */ static final Set<String> sKnownTypeSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
+ "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
+ "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
+ "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
+ "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
+ "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
+ "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
+ "WAVE", "AIFF", "PCM", "X509", "PGP")));
+
+ /**
+ * A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
+ */
+ /* package */ static final Set<String> sKnownValueSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
+
+ /**
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
+ * </p>
+ * <p>
* Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
- * We allow it for safety...
+ * We allow it for safety.
+ * </p>
*/
- private static final HashSet<String> sAvailableEncodingV21 =
- new HashSet<String>(Arrays.asList(
- "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
-
- // Used only for parsing END:VCARD.
- private String mPreviousLine;
-
- /** The builder to build parsed data */
- protected VCardInterpreter mBuilder = null;
+ /* package */ static final Set<String> sAvailableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_QP,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
- /**
- * The encoding type. "Encoding" in vCard is different from "Charset".
- * e.g. 7BIT, 8BIT, QUOTED-PRINTABLE.
- */
- protected String mEncoding = null;
-
- protected final String sDefaultEncoding = "8BIT";
-
- // Should not directly read a line from this object. Use getLine() instead.
- protected BufferedReader mReader;
-
- // In some cases, vCard is nested. Currently, we only consider the most interior vCard data.
- // See v21_foma_1.vcf in test directory for more information.
- private int mNestCount;
-
- // In order to reduce warning message as much as possible, we hold the value which made Logger
- // emit a warning message.
- protected Set<String> mUnknownTypeMap = new HashSet<String>();
- protected Set<String> mUnknownValueMap = new HashSet<String>();
-
- // For measuring performance.
- private long mTimeTotal;
- private long mTimeReadStartRecord;
- private long mTimeReadEndRecord;
- private long mTimeStartProperty;
- private long mTimeEndProperty;
- private long mTimeParseItems;
- private long mTimeParseLineAndHandleGroup;
- private long mTimeParsePropertyValues;
- private long mTimeParseAdrOrgN;
- private long mTimeHandleMiscPropertyValue;
- private long mTimeHandleQuotedPrintable;
- private long mTimeHandleBase64;
+ private final VCardParserImpl_V21 mVCardParserImpl;
public VCardParser_V21() {
- this(null);
+ mVCardParserImpl = new VCardParserImpl_V21();
}
public VCardParser_V21(VCardSourceDetector detector) {
- this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
+ mVCardParserImpl = new VCardParserImpl_V21(detector);
}
public VCardParser_V21(int parseType) {
- super(parseType);
- if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
- mNestCount = 1;
- }
+ mVCardParserImpl = new VCardParserImpl_V21(parseType);
}
- /**
- * Parses the file at the given position.
- *
- * vcard_file = [wsls] vcard [wsls]
- */
- protected void parseVCardFile() throws IOException, VCardException {
- boolean firstReading = true;
- while (true) {
- if (mCanceled) {
- break;
- }
- if (!parseOneVCard(firstReading)) {
- break;
- }
- firstReading = false;
- }
+ //// Implemented methods
- if (mNestCount > 0) {
- boolean useCache = true;
- for (int i = 0; i < mNestCount; i++) {
- readEndVCard(useCache, true);
- useCache = false;
- }
- }
- }
-
- protected int getVersion() {
- return VCardConfig.FLAG_V21;
- }
-
- protected String getVersionString() {
- return VCardConstants.VERSION_V21;
- }
-
- /**
- * @return true when the propertyName is a valid property name.
- */
- protected boolean isValidPropertyName(String propertyName) {
- if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
- propertyName.startsWith("X-")) &&
- !mUnknownTypeMap.contains(propertyName)) {
- mUnknownTypeMap.add(propertyName);
- Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
- }
- return true;
- }
-
- /**
- * @return true when the encoding is a valid encoding.
- */
- protected boolean isValidEncoding(String encoding) {
- return sAvailableEncodingV21.contains(encoding.toUpperCase());
- }
-
- /**
- * @return String. It may be null, or its length may be 0
- * @throws IOException
- */
- protected String getLine() throws IOException {
- return mReader.readLine();
- }
-
- /**
- * @return String with it's length > 0
- * @throws IOException
- * @throws VCardException when the stream reached end of line
- */
- protected String getNonEmptyLine() throws IOException, VCardException {
- String line;
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException("Reached end of buffer.");
- } else if (line.trim().length() > 0) {
- return line;
- }
- }
- }
-
- /**
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF
- * "END" [ws] ":" [ws] "VCARD"
- */
- private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException {
- boolean allowGarbage = false;
- if (firstReading) {
- if (mNestCount > 0) {
- for (int i = 0; i < mNestCount; i++) {
- if (!readBeginVCard(allowGarbage)) {
- return false;
- }
- allowGarbage = true;
- }
- }
- }
-
- if (!readBeginVCard(allowGarbage)) {
- return false;
- }
- long start;
- if (mBuilder != null) {
- start = System.currentTimeMillis();
- mBuilder.startEntry();
- mTimeReadStartRecord += System.currentTimeMillis() - start;
- }
- start = System.currentTimeMillis();
- parseItems();
- mTimeParseItems += System.currentTimeMillis() - start;
- readEndVCard(true, false);
- if (mBuilder != null) {
- start = System.currentTimeMillis();
- mBuilder.endEntry();
- mTimeReadEndRecord += System.currentTimeMillis() - start;
- }
- return true;
- }
-
- /**
- * @return True when successful. False when reaching the end of line
- * @throws IOException
- * @throws VCardException
- */
- protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
- String line;
- do {
- while (true) {
- line = getLine();
- if (line == null) {
- return false;
- } else if (line.trim().length() > 0) {
- break;
- }
- }
- String[] strArray = line.split(":", 2);
- int length = strArray.length;
-
- // Though vCard 2.1/3.0 specification does not allow lower cases,
- // vCard file emitted by some external vCard expoter have such invalid Strings.
- // So we allow it.
- // e.g. BEGIN:vCard
- if (length == 2 &&
- strArray[0].trim().equalsIgnoreCase("BEGIN") &&
- strArray[1].trim().equalsIgnoreCase("VCARD")) {
- return true;
- } else if (!allowGarbage) {
- if (mNestCount > 0) {
- mPreviousLine = line;
- return false;
- } else {
- throw new VCardException(
- "Expected String \"BEGIN:VCARD\" did not come "
- + "(Instead, \"" + line + "\" came)");
- }
- }
- } while(allowGarbage);
-
- throw new VCardException("Reached where must not be reached.");
- }
-
- /**
- * The arguments useCache and allowGarbase are usually true and false accordingly when
- * this function is called outside this function itself.
- *
- * @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine()
- * is used.
- * @param allowGarbage When true, ignore non "END:VCARD" line.
- * @throws IOException
- * @throws VCardException
- */
- protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ public boolean parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
- String line;
- do {
- if (useCache) {
- // Though vCard specification does not allow lower cases,
- // some data may have them, so we allow it.
- line = mPreviousLine;
- } else {
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException("Expected END:VCARD was not found.");
- } else if (line.trim().length() > 0) {
- break;
- }
- }
- }
-
- String[] strArray = line.split(":", 2);
- if (strArray.length == 2 &&
- strArray[0].trim().equalsIgnoreCase("END") &&
- strArray[1].trim().equalsIgnoreCase("VCARD")) {
- return;
- } else if (!allowGarbage) {
- throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
- }
- useCache = false;
- } while (allowGarbage);
- }
-
- /**
- * items = *CRLF item
- * / item
- */
- protected void parseItems() throws IOException, VCardException {
- boolean ended = false;
-
- if (mBuilder != null) {
- long start = System.currentTimeMillis();
- mBuilder.startProperty();
- mTimeStartProperty += System.currentTimeMillis() - start;
- }
- ended = parseItem();
- if (mBuilder != null && !ended) {
- long start = System.currentTimeMillis();
- mBuilder.endProperty();
- mTimeEndProperty += System.currentTimeMillis() - start;
- }
-
- while (!ended) {
- // follow VCARD ,it wont reach endProperty
- if (mBuilder != null) {
- long start = System.currentTimeMillis();
- mBuilder.startProperty();
- mTimeStartProperty += System.currentTimeMillis() - start;
- }
- try {
- ended = parseItem();
- } catch (VCardInvalidCommentLineException e) {
- Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
- ended = false;
- }
- if (mBuilder != null && !ended) {
- long start = System.currentTimeMillis();
- mBuilder.endProperty();
- mTimeEndProperty += System.currentTimeMillis() - start;
- }
- }
- }
-
- /**
- * item = [groups "."] name [params] ":" value CRLF
- * / [groups "."] "ADR" [params] ":" addressparts CRLF
- * / [groups "."] "ORG" [params] ":" orgparts CRLF
- * / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
- */
- protected boolean parseItem() throws IOException, VCardException {
- mEncoding = sDefaultEncoding;
-
- final String line = getNonEmptyLine();
- long start = System.currentTimeMillis();
-
- String[] propertyNameAndValue = separateLineAndHandleGroup(line);
- if (propertyNameAndValue == null) {
- return true;
- }
- if (propertyNameAndValue.length != 2) {
- throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
- }
- String propertyName = propertyNameAndValue[0].toUpperCase();
- String propertyValue = propertyNameAndValue[1];
-
- mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
-
- if (propertyName.equals("ADR") || propertyName.equals("ORG") ||
- propertyName.equals("N")) {
- start = System.currentTimeMillis();
- handleMultiplePropertyValue(propertyName, propertyValue);
- mTimeParseAdrOrgN += System.currentTimeMillis() - start;
- return false;
- } else if (propertyName.equals("AGENT")) {
- handleAgent(propertyValue);
- return false;
- } else if (isValidPropertyName(propertyName)) {
- if (propertyName.equals("BEGIN")) {
- if (propertyValue.equals("VCARD")) {
- throw new VCardNestedException("This vCard has nested vCard data in it.");
- } else {
- throw new VCardException("Unknown BEGIN type: " + propertyValue);
- }
- } else if (propertyName.equals("VERSION") &&
- !propertyValue.equals(getVersionString())) {
- throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersionString());
- }
- start = System.currentTimeMillis();
- handlePropertyValue(propertyName, propertyValue);
- mTimeParsePropertyValues += System.currentTimeMillis() - start;
- return false;
- }
-
- throw new VCardException("Unknown property name: \"" + propertyName + "\"");
+ return mVCardParserImpl.parse(is, VCardConfig.DEFAULT_TEMPORARY_CHARSET, interepreter);
}
- static private final int STATE_GROUP_OR_PROPNAME = 0;
- static private final int STATE_PARAMS = 1;
- // vCard 3.0 specification allows double-quoted param-value, while vCard 2.1 does not.
- // This is just for safety.
- static private final int STATE_PARAMS_IN_DQUOTE = 2;
-
- protected String[] separateLineAndHandleGroup(String line) throws VCardException {
- int state = STATE_GROUP_OR_PROPNAME;
- int nameIndex = 0;
-
- final String[] propertyNameAndValue = new String[2];
-
- final int length = line.length();
- if (length > 0 && line.charAt(0) == '#') {
- throw new VCardInvalidCommentLineException();
- }
-
- for (int i = 0; i < length; i++) {
- char ch = line.charAt(i);
- switch (state) {
- case STATE_GROUP_OR_PROPNAME: {
- if (ch == ':') {
- final String propertyName = line.substring(nameIndex, i);
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
- return null;
- }
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- if (i < length - 1) {
- propertyNameAndValue[1] = line.substring(i + 1);
- } else {
- propertyNameAndValue[1] = "";
- }
- return propertyNameAndValue;
- } else if (ch == '.') {
- String groupName = line.substring(nameIndex, i);
- if (mBuilder != null) {
- mBuilder.propertyGroup(groupName);
- }
- nameIndex = i + 1;
- } else if (ch == ';') {
- String propertyName = line.substring(nameIndex, i);
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
- return null;
- }
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- nameIndex = i + 1;
- state = STATE_PARAMS;
- }
- break;
- }
- case STATE_PARAMS: {
- if (ch == '"') {
- state = STATE_PARAMS_IN_DQUOTE;
- } else if (ch == ';') {
- handleParams(line.substring(nameIndex, i));
- nameIndex = i + 1;
- } else if (ch == ':') {
- handleParams(line.substring(nameIndex, i));
- if (i < length - 1) {
- propertyNameAndValue[1] = line.substring(i + 1);
- } else {
- propertyNameAndValue[1] = "";
- }
- return propertyNameAndValue;
- }
- break;
- }
- case STATE_PARAMS_IN_DQUOTE: {
- if (ch == '"') {
- state = STATE_PARAMS;
- }
- break;
- }
- }
- }
-
- throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
- }
-
- /**
- * params = ";" [ws] paramlist
- * paramlist = paramlist [ws] ";" [ws] param
- * / param
- * param = "TYPE" [ws] "=" [ws] ptypeval
- * / "VALUE" [ws] "=" [ws] pvalueval
- * / "ENCODING" [ws] "=" [ws] pencodingval
- * / "CHARSET" [ws] "=" [ws] charsetval
- * / "LANGUAGE" [ws] "=" [ws] langval
- * / "X-" word [ws] "=" [ws] word
- * / knowntype
- */
- protected void handleParams(String params) throws VCardException {
- String[] strArray = params.split("=", 2);
- if (strArray.length == 2) {
- final String paramName = strArray[0].trim().toUpperCase();
- String paramValue = strArray[1].trim();
- if (paramName.equals("TYPE")) {
- handleType(paramValue);
- } else if (paramName.equals("VALUE")) {
- handleValue(paramValue);
- } else if (paramName.equals("ENCODING")) {
- handleEncoding(paramValue);
- } else if (paramName.equals("CHARSET")) {
- handleCharset(paramValue);
- } else if (paramName.equals("LANGUAGE")) {
- handleLanguage(paramValue);
- } else if (paramName.startsWith("X-")) {
- handleAnyParam(paramName, paramValue);
- } else {
- throw new VCardException("Unknown type \"" + paramName + "\"");
- }
- } else {
- handleParamWithoutName(strArray[0]);
- }
- }
-
- /**
- * vCard 3.0 parser may throw VCardException.
- */
- @SuppressWarnings("unused")
- protected void handleParamWithoutName(final String paramValue) throws VCardException {
- handleType(paramValue);
- }
-
- /**
- * ptypeval = knowntype / "X-" word
- */
- protected void handleType(final String ptypeval) {
- String upperTypeValue = ptypeval;
- if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) &&
- !mUnknownTypeMap.contains(ptypeval)) {
- mUnknownTypeMap.add(ptypeval);
- Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("TYPE");
- mBuilder.propertyParamValue(upperTypeValue);
- }
- }
-
- /**
- * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
- */
- protected void handleValue(final String pvalueval) {
- if (!sKnownValueSet.contains(pvalueval.toUpperCase()) &&
- pvalueval.startsWith("X-") &&
- !mUnknownValueMap.contains(pvalueval)) {
- mUnknownValueMap.add(pvalueval);
- Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval);
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("VALUE");
- mBuilder.propertyParamValue(pvalueval);
- }
- }
-
- /**
- * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
- */
- protected void handleEncoding(String pencodingval) throws VCardException {
- if (isValidEncoding(pencodingval) ||
- pencodingval.startsWith("X-")) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("ENCODING");
- mBuilder.propertyParamValue(pencodingval);
- }
- mEncoding = pencodingval;
- } else {
- throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
- }
- }
-
- /**
- * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
- * but today's vCard often contains other charset, so we allow them.
- */
- protected void handleCharset(String charsetval) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("CHARSET");
- mBuilder.propertyParamValue(charsetval);
- }
- }
-
- /**
- * See also Section 7.1 of RFC 1521
- */
- protected void handleLanguage(String langval) throws VCardException {
- String[] strArray = langval.split("-");
- if (strArray.length != 2) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- String tmp = strArray[0];
- int length = tmp.length();
- for (int i = 0; i < length; i++) {
- if (!isLetter(tmp.charAt(i))) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- }
- tmp = strArray[1];
- length = tmp.length();
- for (int i = 0; i < length; i++) {
- if (!isLetter(tmp.charAt(i))) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("LANGUAGE");
- mBuilder.propertyParamValue(langval);
- }
- }
-
- /**
- * Mainly for "X-" type. This accepts any kind of type without check.
- */
- protected void handleAnyParam(String paramName, String paramValue) {
- if (mBuilder != null) {
- mBuilder.propertyParamType(paramName);
- mBuilder.propertyParamValue(paramValue);
- }
- }
-
- protected void handlePropertyValue(String propertyName, String propertyValue)
+ public boolean parse(InputStream is, String charset, VCardInterpreter interpreter)
throws IOException, VCardException {
- if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
- final long start = System.currentTimeMillis();
- final String result = getQuotedPrintable(propertyValue);
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(result);
- mBuilder.propertyValues(v);
- }
- mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
- } else if (mEncoding.equalsIgnoreCase("BASE64") ||
- mEncoding.equalsIgnoreCase("B")) {
- final long start = System.currentTimeMillis();
- // It is very rare, but some BASE64 data may be so big that
- // OutOfMemoryError occurs. To ignore such cases, use try-catch.
- try {
- final String result = getBase64(propertyValue);
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(result);
- mBuilder.propertyValues(v);
- }
- } catch (OutOfMemoryError error) {
- Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
- if (mBuilder != null) {
- mBuilder.propertyValues(null);
- }
- }
- mTimeHandleBase64 += System.currentTimeMillis() - start;
- } else {
- if (!(mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
- || mEncoding.equalsIgnoreCase("8BIT")
- || mEncoding.toUpperCase().startsWith("X-"))) {
- Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\".");
- }
+ return mVCardParserImpl.parse(is, charset, interpreter);
+ }
- final long start = System.currentTimeMillis();
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(maybeUnescapeText(propertyValue));
- mBuilder.propertyValues(v);
- }
- mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
- }
- }
-
- protected String getQuotedPrintable(String firstString) throws IOException, VCardException {
- // Specifically, there may be some padding between = and CRLF.
- // See the following:
- //
- // qp-line := *(qp-segment transport-padding CRLF)
- // qp-part transport-padding
- // qp-segment := qp-section *(SPACE / TAB) "="
- // ; Maximum length of 76 characters
- //
- // e.g. (from RFC 2045)
- // Now's the time =
- // for all folk to come=
- // to the aid of their country.
- if (firstString.trim().endsWith("=")) {
- // remove "transport-padding"
- int pos = firstString.length() - 1;
- while(firstString.charAt(pos) != '=') {
- }
- StringBuilder builder = new StringBuilder();
- builder.append(firstString.substring(0, pos + 1));
- builder.append("\r\n");
- String line;
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing quoted-printable String");
- }
- if (line.trim().endsWith("=")) {
- // remove "transport-padding"
- pos = line.length() - 1;
- while(line.charAt(pos) != '=') {
- }
- builder.append(line.substring(0, pos + 1));
- builder.append("\r\n");
- } else {
- builder.append(line);
- break;
- }
- }
- return builder.toString();
- } else {
- return firstString;
- }
- }
-
- protected String getBase64(String firstString) throws IOException, VCardException {
- StringBuilder builder = new StringBuilder();
- builder.append(firstString);
-
- while (true) {
- String line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing BASE64 binary");
- }
- if (line.length() == 0) {
- break;
- }
- builder.append(line);
- }
-
- return builder.toString();
- }
-
- /**
- * Mainly for "ADR", "ORG", and "N"
- * We do not care the number of strnosemi here.
- *
- * addressparts = 0*6(strnosemi ";") strnosemi
- * ; PO Box, Extended Addr, Street, Locality, Region,
- * Postal Code, Country Name
- * orgparts = *(strnosemi ";") strnosemi
- * ; First is Organization Name,
- * remainder are Organization Units.
- * nameparts = 0*4(strnosemi ";") strnosemi
- * ; Family, Given, Middle, Prefix, Suffix.
- * ; Example:Public;John;Q.;Reverend Dr.;III, Esq.
- * strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi
- * ; To include a semicolon in this string, it must be escaped
- * ; with a "\" character.
- *
- * We are not sure whether we should add "\" CRLF to each value.
- * For now, we exclude them.
- */
- protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
+ public boolean parse(InputStream is, String charset,
+ VCardInterpreter interpreter, boolean canceled)
throws IOException, VCardException {
- // vCard 2.1 does not allow QUOTED-PRINTABLE here,
- // but some softwares/devices emit such data.
- if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
- propertyValue = getQuotedPrintable(propertyValue);
- }
-
- if (mBuilder != null) {
- mBuilder.propertyValues(VCardUtils.constructListFromValue(
- propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
- }
+ return mVCardParserImpl.parse(is, charset, interpreter, canceled);
}
- /**
- * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
- *
- * item = ...
- * / [groups "."] "AGENT"
- * [params] ":" vcard CRLF
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF "END" [ws] ":" [ws] "VCARD"
- */
- protected void handleAgent(final String propertyValue) throws VCardException {
- if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
- // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
- return;
- } else {
- throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
- }
- // TODO: Support AGENT property.
- }
-
- /**
- * For vCard 3.0.
- */
- protected String maybeUnescapeText(final String text) {
- return text;
- }
-
- /**
- * Returns unescaped String if the character should be unescaped. Return null otherwise.
- * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
- */
- protected String maybeUnescapeCharacter(final char ch) {
- return unescapeCharacter(ch);
- }
-
- public static String unescapeCharacter(final char ch) {
- // Original vCard 2.1 specification does not allow transformation
- // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
- // this class allowed them, so keep it as is.
- if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
- return String.valueOf(ch);
- } else {
- return null;
- }
- }
-
- @Override
- public boolean parse(final InputStream is, final VCardInterpreter builder)
- throws IOException, VCardException {
- return parse(is, VCardConfig.DEFAULT_CHARSET, builder);
- }
-
- @Override
- public boolean parse(InputStream is, String charset, VCardInterpreter builder)
- throws IOException, VCardException {
- if (charset == null) {
- charset = VCardConfig.DEFAULT_CHARSET;
- }
- final InputStreamReader tmpReader = new InputStreamReader(is, charset);
- if (VCardConfig.showPerformanceLog()) {
- mReader = new CustomBufferedReader(tmpReader);
- } else {
- mReader = new BufferedReader(tmpReader);
- }
-
- mBuilder = builder;
-
- long start = System.currentTimeMillis();
- if (mBuilder != null) {
- mBuilder.start();
- }
- parseVCardFile();
- if (mBuilder != null) {
- mBuilder.end();
- }
- mTimeTotal += System.currentTimeMillis() - start;
-
- if (VCardConfig.showPerformanceLog()) {
- showPerformanceInfo();
- }
-
- return true;
- }
-
- @Override
- public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled)
- throws IOException, VCardException {
- mCanceled = canceled;
- parse(is, charset, builder);
- }
-
- private void showPerformanceInfo() {
- Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
- if (mReader instanceof CustomBufferedReader) {
- Log.d(LOG_TAG, "Total readLine time: " +
- ((CustomBufferedReader)mReader).getTotalmillisecond() + " ms");
- }
- Log.d(LOG_TAG, "Time for handling the beggining of the record: " +
- mTimeReadStartRecord + " ms");
- Log.d(LOG_TAG, "Time for handling the end of the record: " +
- mTimeReadEndRecord + " ms");
- Log.d(LOG_TAG, "Time for parsing line, and handling group: " +
- mTimeParseLineAndHandleGroup + " ms");
- Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
- Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
- Log.d(LOG_TAG, "Time for handling normal property values: " +
- mTimeHandleMiscPropertyValue + " ms");
- Log.d(LOG_TAG, "Time for handling Quoted-Printable: " +
- mTimeHandleQuotedPrintable + " ms");
- Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
- }
-
- private boolean isLetter(char ch) {
- if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
- return true;
- }
- return false;
- }
-}
-
-class CustomBufferedReader extends BufferedReader {
- private long mTime;
-
- public CustomBufferedReader(Reader in) {
- super(in);
- }
-
- @Override
- public String readLine() throws IOException {
- long start = System.currentTimeMillis();
- String ret = super.readLine();
- long end = System.currentTimeMillis();
- mTime += end - start;
- return ret;
- }
-
- public long getTotalmillisecond() {
- return mTime;
+ public void cancel() {
+ mVCardParserImpl.cancel();
}
}
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 4ecfe97..13b4196 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -16,343 +16,89 @@
package android.pim.vcard;
import android.pim.vcard.exception.VCardException;
-import android.util.Log;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Set;
/**
- * The class used to parse vCard 3.0.
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
+ * <p>
+ * vCard parser for vCard 3.0. See RFC 2426 for more detail.
+ * </p>
+ * <p>
+ * This parser allows vCard format which is not allowed in the RFC, since
+ * we have seen several vCard 3.0 files which don't comply with it.
+ * </p>
+ * <p>
+ * e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
+ * have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
+ * but it is not a must. We silently allow "CHARSET".
+ * </p>
*/
-public class VCardParser_V30 extends VCardParser_V21 {
- private static final String LOG_TAG = "VCardParser_V30";
-
- private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
- Arrays.asList(
+public class VCardParser_V30 implements VCardParser {
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
"NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
- "SORT-STRING", "CATEGORIES", "PRODID")); // 3.0
-
- // Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety.
- private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>(
- Arrays.asList("7BIT", "8BIT", "BASE64", "B"));
-
- // Although RFC 2426 specifies some property must not have parameters, we allow it,
- // since there may be some careers which violates the RFC...
- private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
-
- private String mPreviousLine;
-
- private boolean mEmittedAgentWarning = false;
+ "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
/**
- * True when the caller wants the parser to be strict about the input.
- * Currently this is only for testing.
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
+ * </p>
+ * <p>
+ * Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
+ * </p>
+ * <p>
+ * "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
+ * because the encoding ambiguates how the vCard file to be parsed.
+ * </p>
*/
- private final boolean mStrictParsing;
+ /* package */ static final Set<String> sAcceptableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
+
+ private final VCardParserImpl_V30 mVCardParserImpl;
public VCardParser_V30() {
- super();
- mStrictParsing = false;
+ mVCardParserImpl = new VCardParserImpl_V30();
}
- /**
- * @param strictParsing when true, this object throws VCardException when the vcard is not
- * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
- * is not fully yet for being used with this flag and may not notice invalid line(s).
- *
- * @hide currently only for testing!
- */
- public VCardParser_V30(boolean strictParsing) {
- super();
- mStrictParsing = strictParsing;
+ public VCardParser_V30(VCardSourceDetector detector) {
+ mVCardParserImpl = new VCardParserImpl_V30(detector);
}
- public VCardParser_V30(int parseMode) {
- super(parseMode);
- mStrictParsing = false;
+ public VCardParser_V30(int parseType) {
+ mVCardParserImpl = new VCardParserImpl_V30(parseType);
}
- @Override
- protected int getVersion() {
- return VCardConfig.FLAG_V30;
- }
-
- @Override
- protected String getVersionString() {
- return VCardConstants.VERSION_V30;
- }
-
- @Override
- protected boolean isValidPropertyName(String propertyName) {
- if (!(sAcceptablePropsWithParam.contains(propertyName) ||
- acceptablePropsWithoutParam.contains(propertyName) ||
- propertyName.startsWith("X-")) &&
- !mUnknownTypeMap.contains(propertyName)) {
- mUnknownTypeMap.add(propertyName);
- Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
- }
- return true;
- }
-
- @Override
- protected boolean isValidEncoding(String encoding) {
- return sAcceptableEncodingV30.contains(encoding.toUpperCase());
- }
-
- @Override
- protected String getLine() throws IOException {
- if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- } else {
- return mReader.readLine();
- }
- }
+ //// Implemented methods
- /**
- * vCard 3.0 requires that the line with space at the beginning of the line
- * must be combined with previous line.
- */
- @Override
- protected String getNonEmptyLine() throws IOException, VCardException {
- String line;
- StringBuilder builder = null;
- while (true) {
- line = mReader.readLine();
- if (line == null) {
- if (builder != null) {
- return builder.toString();
- } else if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- }
- throw new VCardException("Reached end of buffer.");
- } else if (line.length() == 0) {
- if (builder != null) {
- return builder.toString();
- } else if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- }
- } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
- if (builder != null) {
- // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
- // Following is the excerpts from it.
- //
- // DESCRIPTION:This is a long description that exists on a long line.
- //
- // Can be represented as:
- //
- // DESCRIPTION:This is a long description
- // that exists on a long line.
- //
- // It could also be represented as:
- //
- // DESCRIPTION:This is a long descrip
- // tion that exists o
- // n a long line.
- builder.append(line.substring(1));
- } else if (mPreviousLine != null) {
- builder = new StringBuilder();
- builder.append(mPreviousLine);
- mPreviousLine = null;
- builder.append(line.substring(1));
- } else {
- throw new VCardException("Space exists at the beginning of the line");
- }
- } else {
- if (mPreviousLine == null) {
- mPreviousLine = line;
- if (builder != null) {
- return builder.toString();
- }
- } else {
- String ret = mPreviousLine;
- mPreviousLine = line;
- return ret;
- }
- }
- }
- }
-
-
- /**
- * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
- * 1 * (contentline)
- * ;A vCard object MUST include the VERSION, FN and N types.
- * [group "."] "END" ":" "VCARD" 1 * CRLF
- */
- @Override
- protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
- // TODO: vCard 3.0 supports group.
- return super.readBeginVCard(allowGarbage);
- }
-
- @Override
- protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ public boolean parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
- // TODO: vCard 3.0 supports group.
- super.readEndVCard(useCache, allowGarbage);
+ return mVCardParserImpl.parse(is, VCardConfig.DEFAULT_TEMPORARY_CHARSET, interepreter);
}
- /**
- * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
- */
- @Override
- protected void handleParams(String params) throws VCardException {
- try {
- super.handleParams(params);
- } catch (VCardException e) {
- // maybe IANA type
- String[] strArray = params.split("=", 2);
- if (strArray.length == 2) {
- handleAnyParam(strArray[0], strArray[1]);
- } else {
- // Must not come here in the current implementation.
- throw new VCardException(
- "Unknown params value: " + params);
- }
- }
+ public boolean parse(InputStream is, String charset, VCardInterpreter interpreter)
+ throws IOException, VCardException {
+ return mVCardParserImpl.parse(is, charset, interpreter);
}
- @Override
- protected void handleAnyParam(String paramName, String paramValue) {
- super.handleAnyParam(paramName, paramValue);
+ public boolean parse(InputStream is, String charset,
+ VCardInterpreter interpreter, boolean canceled)
+ throws IOException, VCardException {
+ return mVCardParserImpl.parse(is, charset, interpreter, canceled);
}
- @Override
- protected void handleParamWithoutName(final String paramValue) throws VCardException {
- if (mStrictParsing) {
- throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
- } else {
- super.handleParamWithoutName(paramValue);
- }
- }
-
- /**
- * vCard 3.0 defines
- *
- * param = param-name "=" param-value *("," param-value)
- * param-name = iana-token / x-name
- * param-value = ptext / quoted-string
- * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
- */
- @Override
- protected void handleType(String ptypevalues) {
- String[] ptypeArray = ptypevalues.split(",");
- mBuilder.propertyParamType("TYPE");
- for (String value : ptypeArray) {
- int length = value.length();
- if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
- mBuilder.propertyParamValue(value.substring(1, value.length() - 1));
- } else {
- mBuilder.propertyParamValue(value);
- }
- }
- }
-
- @Override
- protected void handleAgent(String propertyValue) {
- // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
- //
- // e.g.
- // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
- // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
- // ET:jfriday@host.com\nEND:VCARD\n
- //
- // TODO: fix this.
- //
- // issue:
- // vCard 3.0 also allows this as an example.
- //
- // AGENT;VALUE=uri:
- // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
- //
- // This is not vCard. Should we support this?
- //
- // Just ignore the line for now, since we cannot know how to handle it...
- if (!mEmittedAgentWarning) {
- Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
- mEmittedAgentWarning = true;
- }
- }
-
- /**
- * vCard 3.0 does not require two CRLF at the last of BASE64 data.
- * It only requires that data should be MIME-encoded.
- */
- @Override
- protected String getBase64(String firstString) throws IOException, VCardException {
- StringBuilder builder = new StringBuilder();
- builder.append(firstString);
-
- while (true) {
- String line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing BASE64 binary");
- }
- if (line.length() == 0) {
- break;
- } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
- mPreviousLine = line;
- break;
- }
- builder.append(line);
- }
-
- return builder.toString();
- }
-
- /**
- * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
- * ; \\ encodes \, \n or \N encodes newline
- * ; \; encodes ;, \, encodes ,
- *
- * Note: Apple escapes ':' into '\:' while does not escape '\'
- */
- @Override
- protected String maybeUnescapeText(String text) {
- return unescapeText(text);
- }
-
- public static String unescapeText(String text) {
- StringBuilder builder = new StringBuilder();
- int length = text.length();
- for (int i = 0; i < length; i++) {
- char ch = text.charAt(i);
- if (ch == '\\' && i < length - 1) {
- char next_ch = text.charAt(++i);
- if (next_ch == 'n' || next_ch == 'N') {
- builder.append("\n");
- } else {
- builder.append(next_ch);
- }
- } else {
- builder.append(ch);
- }
- }
- return builder.toString();
- }
-
- @Override
- protected String maybeUnescapeCharacter(char ch) {
- return unescapeCharacter(ch);
- }
-
- public static String unescapeCharacter(char ch) {
- if (ch == 'n' || ch == 'N') {
- return "\n";
- } else {
- return String.valueOf(ch);
- }
+ public void cancel() {
+ mVCardParserImpl.cancel();
}
}
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 11b112b..26ebed7 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -329,8 +329,8 @@
if (ch == '\\' && i < length - 1) {
char nextCh = value.charAt(i + 1);
final String unescapedString =
- (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
- VCardParser_V21.unescapeCharacter(nextCh));
+ (isV30 ? VCardParserImpl_V30.unescapeCharacter(nextCh) :
+ VCardParserImpl_V21.unescapeCharacter(nextCh));
if (unescapedString != null) {
builder.append(unescapedString);
i++;
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 726793d..4686978 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -23,7 +23,10 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.text.TextUtils;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
/**
* Shows a hierarchy of {@link Preference} objects as
@@ -69,30 +72,43 @@
* As a convenience, this activity implements a click listener for any
* preference in the current hierarchy, see
* {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
- *
+ *
* @see Preference
* @see PreferenceScreen
*/
public abstract class PreferenceActivity extends ListActivity implements
PreferenceManager.OnPreferenceTreeClickListener {
-
+
private static final String PREFERENCES_TAG = "android:preferences";
-
+
+ // extras that allow any preference activity to be launched as part of a wizard
+
+ // show Back and Next buttons? takes boolean parameter
+ // Back will then return RESULT_CANCELED and Next RESULT_OK
+ private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
+
+ // specify custom text for the Back or Next buttons, or cause a button to not appear
+ // at all by setting it to null
+ private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
+ private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
+
+ private Button mNextButton;
+
private PreferenceManager mPreferenceManager;
-
+
private Bundle mSavedInstanceState;
/**
* The starting request code given out to preference framework.
*/
private static final int FIRST_REQUEST_CODE = 100;
-
+
private static final int MSG_BIND_PREFERENCES = 0;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
-
+
case MSG_BIND_PREFERENCES:
bindPreferences();
break;
@@ -105,7 +121,49 @@
super.onCreate(savedInstanceState);
setContentView(com.android.internal.R.layout.preference_list_content);
-
+
+ // see if we should show Back/Next buttons
+ Intent intent = getIntent();
+ if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
+
+ findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
+
+ Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
+ backButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+ mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
+ mNextButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ setResult(RESULT_OK);
+ finish();
+ }
+ });
+
+ // set our various button parameters
+ if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
+ String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
+ if (TextUtils.isEmpty(buttonText)) {
+ mNextButton.setVisibility(View.GONE);
+ }
+ else {
+ mNextButton.setText(buttonText);
+ }
+ }
+ if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
+ String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
+ if (TextUtils.isEmpty(buttonText)) {
+ backButton.setVisibility(View.GONE);
+ }
+ else {
+ backButton.setText(buttonText);
+ }
+ }
+ }
+
mPreferenceManager = onCreatePreferenceManager();
getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
}
@@ -113,14 +171,13 @@
@Override
protected void onStop() {
super.onStop();
-
+
mPreferenceManager.dispatchActivityStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
-
mPreferenceManager.dispatchActivityDestroy();
}
@@ -156,7 +213,7 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
-
+
mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
}
@@ -176,7 +233,7 @@
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}
-
+
private void bindPreferences() {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
@@ -187,10 +244,10 @@
}
}
}
-
+
/**
* Creates the {@link PreferenceManager}.
- *
+ *
* @return The {@link PreferenceManager} used by this activity.
*/
private PreferenceManager onCreatePreferenceManager() {
@@ -198,7 +255,7 @@
preferenceManager.setOnPreferenceTreeClickListener(this);
return preferenceManager;
}
-
+
/**
* Returns the {@link PreferenceManager} used by this activity.
* @return The {@link PreferenceManager}.
@@ -206,7 +263,7 @@
public PreferenceManager getPreferenceManager() {
return mPreferenceManager;
}
-
+
private void requirePreferenceManager() {
if (mPreferenceManager == null) {
throw new RuntimeException("This should be called after super.onCreate.");
@@ -215,7 +272,7 @@
/**
* Sets the root of the preference hierarchy that this activity is showing.
- *
+ *
* @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
*/
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
@@ -228,37 +285,37 @@
}
}
}
-
+
/**
* Gets the root of the preference hierarchy that this activity is showing.
- *
+ *
* @return The {@link PreferenceScreen} that is the root of the preference
* hierarchy.
*/
public PreferenceScreen getPreferenceScreen() {
return mPreferenceManager.getPreferenceScreen();
}
-
+
/**
* Adds preferences from activities that match the given {@link Intent}.
- *
+ *
* @param intent The {@link Intent} to query activities.
*/
public void addPreferencesFromIntent(Intent intent) {
requirePreferenceManager();
-
+
setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
}
-
+
/**
* Inflates the given XML resource and adds the preference hierarchy to the current
* preference hierarchy.
- *
+ *
* @param preferencesResId The XML resource ID to inflate.
*/
public void addPreferencesFromResource(int preferencesResId) {
requirePreferenceManager();
-
+
setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
getPreferenceScreen()));
}
@@ -269,20 +326,20 @@
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
return false;
}
-
+
/**
* Finds a {@link Preference} based on its key.
- *
+ *
* @param key The key of the preference to retrieve.
* @return The {@link Preference} with the key, or null.
* @see PreferenceGroup#findPreference(CharSequence)
*/
public Preference findPreference(CharSequence key) {
-
+
if (mPreferenceManager == null) {
return null;
}
-
+
return mPreferenceManager.findPreference(key);
}
@@ -292,5 +349,14 @@
mPreferenceManager.dispatchNewIntent(intent);
}
}
-
+
+ // give subclasses access to the Next button
+ /** @hide */
+ protected boolean hasNextButton() {
+ return mNextButton != null;
+ }
+ /** @hide */
+ protected Button getNextButton() {
+ return mNextButton;
+ }
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 40a408a..1531392 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -5558,6 +5558,28 @@
"com.android.contacts.action.SHOW_OR_CREATE_CONTACT";
/**
+ * Starts an Activity that lets the user select the multiple phones from a
+ * list of phone numbers which come from the contacts or
+ * {@link #EXTRA_PHONE_URIS}.
+ * <p>
+ * The phone numbers being passed in through {@link #EXTRA_PHONE_URIS}
+ * could belong to the contacts or not, and will be selected by default.
+ * <p>
+ * The user's selection will be returned from
+ * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
+ * if the resultCode is
+ * {@link android.app.Activity#RESULT_OK}, the array of picked phone
+ * numbers are in the Intent's
+ * {@link #EXTRA_PHONE_URIS}; otherwise, the
+ * {@link android.app.Activity#RESULT_CANCELED} is returned if the user
+ * left the Activity without changing the selection.
+ *
+ * @hide
+ */
+ public static final String ACTION_GET_MULTIPLE_PHONES =
+ "com.android.contacts.action.GET_MULTIPLE_PHONES";
+
+ /**
* Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new
* contact if no matching contact found. Otherwise, default behavior is
* to prompt user with dialog before creating.
@@ -5578,6 +5600,23 @@
"com.android.contacts.action.CREATE_DESCRIPTION";
/**
+ * Used with {@link #ACTION_GET_MULTIPLE_PHONES} as the input or output value.
+ * <p>
+ * The phone numbers want to be picked by default should be passed in as
+ * input value. These phone numbers could belong to the contacts or not.
+ * <p>
+ * The phone numbers which were picked by the user are returned as output
+ * value.
+ * <p>
+ * Type: array of URIs, the tel URI is used for the phone numbers which don't
+ * belong to any contact, the content URI is used for phone id in contacts.
+ *
+ * @hide
+ */
+ public static final String EXTRA_PHONE_URIS =
+ "com.android.contacts.extra.PHONE_URIS";
+
+ /**
* Optional extra used with {@link #SHOW_OR_CREATE_CONTACT} to specify a
* dialog location using screen coordinates. When not specified, the
* dialog will be centered.
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 14e5655..b6aa03a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -310,7 +310,6 @@
Directions[] objects = new Directions[1];
-
for (int i = 0; i < n; i++) {
ints[START] = reflowed.getLineStart(i) |
(reflowed.getParagraphDirection(i) << DIR_SHIFT) |
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 38ac9b7..ff1f2a60 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,25 +16,31 @@
package android.text;
+import com.android.internal.util.ArrayUtils;
+
import android.emoji.EmojiFactory;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.Path;
-import com.android.internal.util.ArrayUtils;
-
-import junit.framework.Assert;
-import android.text.style.*;
import android.text.method.TextKeyListener;
+import android.text.style.AlignmentSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineBackgroundSpan;
+import android.text.style.ParagraphStyle;
+import android.text.style.ReplacementSpan;
+import android.text.style.TabStopSpan;
import android.view.KeyEvent;
+import junit.framework.Assert;
+
/**
- * A base class that manages text layout in visual elements on
- * the screen.
- * <p>For text that will be edited, use a {@link DynamicLayout},
- * which will be updated as the text changes.
+ * A base class that manages text layout in visual elements on
+ * the screen.
+ * <p>For text that will be edited, use a {@link DynamicLayout},
+ * which will be updated as the text changes.
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
@@ -66,7 +72,7 @@
TextPaint paint) {
return getDesiredWidth(source, 0, source.length(), paint);
}
-
+
/**
* Return how wide a layout must be in order to display the
* specified text slice with one line per paragraph.
@@ -185,13 +191,13 @@
if (dbottom < bottom) {
bottom = dbottom;
}
-
- int first = getLineForVertical(top);
+
+ int first = getLineForVertical(top);
int last = getLineForVertical(bottom);
-
+
int previousLineBottom = getLineTop(first);
int previousLineEnd = getLineStart(first);
-
+
TextPaint paint = mPaint;
CharSequence buf = mText;
int width = mWidth;
@@ -238,7 +244,7 @@
previousLineBottom = getLineTop(first);
previousLineEnd = getLineStart(first);
spans = NO_PARA_SPANS;
- }
+ }
// There can be a highlight even without spans if we are drawing
// a non-spanned transformation of a spanned editing buffer.
@@ -255,7 +261,7 @@
}
Alignment align = mAlignment;
-
+
// Next draw the lines, one at a time.
// the baseline is the top of the following line minus the current
// line's descent.
@@ -271,7 +277,7 @@
int lbaseline = lbottom - getLineDescent(i);
boolean isFirstParaLine = false;
- if (spannedText) {
+ if (spannedText) {
if (start == 0 || buf.charAt(start - 1) == '\n') {
isFirstParaLine = true;
}
@@ -282,7 +288,7 @@
spanend = sp.nextSpanTransition(start, textLength,
ParagraphStyle.class);
spans = sp.getSpans(start, spanend, ParagraphStyle.class);
-
+
align = mAlignment;
for (int n = spans.length-1; n >= 0; n--) {
if (spans[n] instanceof AlignmentSpan) {
@@ -292,7 +298,7 @@
}
}
}
-
+
int dir = getParagraphDirection(i);
int left = 0;
int right = mWidth;
@@ -309,7 +315,7 @@
margin.drawLeadingMargin(c, paint, right, dir, ltop,
lbaseline, lbottom, buf,
start, end, isFirstParaLine, this);
-
+
right -= margin.getLeadingMargin(isFirstParaLine);
} else {
margin.drawLeadingMargin(c, paint, left, dir, ltop,
@@ -417,7 +423,7 @@
mWidth = wid;
}
-
+
/**
* Return the total height of this layout.
*/
@@ -450,7 +456,7 @@
* Return the number of lines of text in this layout.
*/
public abstract int getLineCount();
-
+
/**
* Return the baseline for the specified line (0…getLineCount() - 1)
* If bounds is not null, return the top, left, right, bottom extents
@@ -524,13 +530,95 @@
*/
public abstract int getBottomPadding();
+
+ /**
+ * Returns true if the character at offset and the preceding character
+ * are at different run levels (and thus there's a split caret).
+ * @param offset the offset
+ * @return true if at a level boundary
+ */
+ private boolean isLevelBoundary(int offset) {
+ int line = getLineForOffset(offset);
+ Directions dirs = getLineDirections(line);
+ if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
+ return false;
+ }
+
+ int[] runs = dirs.mDirections;
+ int lineStart = getLineStart(line);
+ int lineEnd = getLineEnd(line);
+ if (offset == lineStart || offset == lineEnd) {
+ int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
+ int runIndex = offset == lineStart ? 0 : runs.length - 2;
+ return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
+ }
+
+ offset -= lineStart;
+ for (int i = 0; i < runs.length; i += 2) {
+ if (offset == runs[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean primaryIsTrailingPrevious(int offset) {
+ int line = getLineForOffset(offset);
+ int lineStart = getLineStart(line);
+ int lineEnd = getLineEnd(line);
+ int[] runs = getLineDirections(line).mDirections;
+
+ int levelAt = -1;
+ for (int i = 0; i < runs.length; i += 2) {
+ int start = lineStart + runs[i];
+ int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+ if (limit > lineEnd) {
+ limit = lineEnd;
+ }
+ if (offset >= start && offset < limit) {
+ if (offset > start) {
+ // Previous character is at same level, so don't use trailing.
+ return false;
+ }
+ levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+ break;
+ }
+ }
+ if (levelAt == -1) {
+ // Offset was limit of line.
+ levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
+ }
+
+ // At level boundary, check previous level.
+ int levelBefore = -1;
+ if (offset == lineStart) {
+ levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
+ } else {
+ offset -= 1;
+ for (int i = 0; i < runs.length; i += 2) {
+ int start = lineStart + runs[i];
+ int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+ if (limit > lineEnd) {
+ limit = lineEnd;
+ }
+ if (offset >= start && offset < limit) {
+ levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+ break;
+ }
+ }
+ }
+
+ return levelBefore < levelAt;
+ }
+
/**
* Get the primary horizontal position for the specified text offset.
* This is the location where a new character would be inserted in
* the paragraph's primary direction.
*/
public float getPrimaryHorizontal(int offset) {
- return getHorizontal(offset, false, true);
+ boolean trailing = primaryIsTrailingPrevious(offset);
+ return getHorizontal(offset, trailing);
}
/**
@@ -539,17 +627,17 @@
* the direction other than the paragraph's primary direction.
*/
public float getSecondaryHorizontal(int offset) {
- return getHorizontal(offset, true, true);
+ boolean trailing = primaryIsTrailingPrevious(offset);
+ return getHorizontal(offset, !trailing);
}
- private float getHorizontal(int offset, boolean trailing, boolean alt) {
+ private float getHorizontal(int offset, boolean trailing) {
int line = getLineForOffset(offset);
- return getHorizontal(offset, trailing, alt, line);
+ return getHorizontal(offset, trailing, line);
}
- private float getHorizontal(int offset, boolean trailing, boolean alt,
- int line) {
+ private float getHorizontal(int offset, boolean trailing, int line) {
int start = getLineStart(line);
int end = getLineVisibleEnd(line);
int dir = getParagraphDirection(line);
@@ -562,7 +650,7 @@
}
float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
- dir, directions, trailing, alt, tab, tabs);
+ dir, directions, trailing, tab, tabs);
if (offset > end) {
if (dir == DIR_RIGHT_TO_LEFT)
@@ -679,7 +767,7 @@
end = getLineEnd(line);
} else {
end = getLineVisibleEnd(line);
- }
+ }
boolean tab = getLineContainsTab(line);
if (tabs == null && tab && mText instanceof Spanned) {
@@ -738,7 +826,7 @@
}
/**
- * Get the character offset on the specfied line whose position is
+ * Get the character offset on the specified line whose position is
* closest to the specified horizontal position.
*/
public int getOffsetForHorizontal(int line, float horiz) {
@@ -752,14 +840,13 @@
int best = min;
float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
- int here = min;
- for (int i = 0; i < dirs.mDirections.length; i++) {
- int there = here + dirs.mDirections[i];
- int swap = ((i & 1) == 0) ? 1 : -1;
+ for (int i = 0; i < dirs.mDirections.length; i += 2) {
+ int here = min + dirs.mDirections[i];
+ int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+ int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
if (there > max)
there = max;
-
int high = there - 1 + 1, low = here + 1 - 1, guess;
while (high - low > 1) {
@@ -792,7 +879,7 @@
if (dist < bestdist) {
bestdist = dist;
- best = low;
+ best = low;
}
}
@@ -802,8 +889,6 @@
bestdist = dist;
best = here;
}
-
- here = there;
}
float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
@@ -823,14 +908,14 @@
return getLineStart(line + 1);
}
- /**
+ /**
* Return the text offset after the last visible character (so whitespace
* is not counted) on the specified line.
*/
public int getLineVisibleEnd(int line) {
return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
}
-
+
private int getLineVisibleEnd(int line, int start, int end) {
if (DEBUG) {
Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
@@ -882,207 +967,178 @@
return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
}
- /**
- * Return the text offset that would be reached by moving left
- * (possibly onto another line) from the specified offset.
- */
public int getOffsetToLeftOf(int offset) {
- int line = getLineForOffset(offset);
- int start = getLineStart(line);
- int end = getLineEnd(line);
- Directions dirs = getLineDirections(line);
-
- if (line != getLineCount() - 1)
- end--;
-
- float horiz = getPrimaryHorizontal(offset);
-
- int best = offset;
- float besth = Integer.MIN_VALUE;
- int candidate;
-
- candidate = TextUtils.getOffsetBefore(mText, offset);
- if (candidate >= start && candidate <= end) {
- float h = getPrimaryHorizontal(candidate);
-
- if (h < horiz && h > besth) {
- best = candidate;
- besth = h;
- }
- }
-
- candidate = TextUtils.getOffsetAfter(mText, offset);
- if (candidate >= start && candidate <= end) {
- float h = getPrimaryHorizontal(candidate);
-
- if (h < horiz && h > besth) {
- best = candidate;
- besth = h;
- }
- }
-
- int here = start;
- for (int i = 0; i < dirs.mDirections.length; i++) {
- int there = here + dirs.mDirections[i];
- if (there > end)
- there = end;
-
- float h = getPrimaryHorizontal(here);
-
- if (h < horiz && h > besth) {
- best = here;
- besth = h;
- }
-
- candidate = TextUtils.getOffsetAfter(mText, here);
- if (candidate >= start && candidate <= end) {
- h = getPrimaryHorizontal(candidate);
-
- if (h < horiz && h > besth) {
- best = candidate;
- besth = h;
- }
- }
-
- candidate = TextUtils.getOffsetBefore(mText, there);
- if (candidate >= start && candidate <= end) {
- h = getPrimaryHorizontal(candidate);
-
- if (h < horiz && h > besth) {
- best = candidate;
- besth = h;
- }
- }
-
- here = there;
- }
-
- float h = getPrimaryHorizontal(end);
-
- if (h < horiz && h > besth) {
- best = end;
- besth = h;
- }
-
- if (best != offset)
- return best;
-
- int dir = getParagraphDirection(line);
-
- if (dir > 0) {
- if (line == 0)
- return best;
- else
- return getOffsetForHorizontal(line - 1, 10000);
- } else {
- if (line == getLineCount() - 1)
- return best;
- else
- return getOffsetForHorizontal(line + 1, 10000);
- }
+ return getOffsetToLeftRightOf(offset, true);
}
- /**
- * Return the text offset that would be reached by moving right
- * (possibly onto another line) from the specified offset.
- */
public int getOffsetToRightOf(int offset) {
- int line = getLineForOffset(offset);
- int start = getLineStart(line);
- int end = getLineEnd(line);
- Directions dirs = getLineDirections(line);
+ return getOffsetToLeftRightOf(offset, false);
+ }
- if (line != getLineCount() - 1)
- end--;
+ // 1) The caret marks the leading edge of a character. The character
+ // logically before it might be on a different level, and the active caret
+ // position is on the character at the lower level. If that character
+ // was the previous character, the caret is on its trailing edge.
+ // 2) Take this character/edge and move it in the indicated direction.
+ // This gives you a new character and a new edge.
+ // 3) This position is between two visually adjacent characters. One of
+ // these might be at a lower level. The active position is on the
+ // character at the lower level.
+ // 4) If the active position is on the trailing edge of the character,
+ // the new caret position is the following logical character, else it
+ // is the character.
+ private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
+ int line = getLineForOffset(caret);
+ int lineStart = getLineStart(line);
+ int lineEnd = getLineEnd(line);
- float horiz = getPrimaryHorizontal(offset);
+ boolean paraIsRtl = getParagraphDirection(line) == -1;
+ int[] runs = getLineDirections(line).mDirections;
- int best = offset;
- float besth = Integer.MAX_VALUE;
- int candidate;
+ int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
+ boolean trailing = false;
- candidate = TextUtils.getOffsetBefore(mText, offset);
- if (candidate >= start && candidate <= end) {
- float h = getPrimaryHorizontal(candidate);
-
- if (h > horiz && h < besth) {
- best = candidate;
- besth = h;
- }
- }
-
- candidate = TextUtils.getOffsetAfter(mText, offset);
- if (candidate >= start && candidate <= end) {
- float h = getPrimaryHorizontal(candidate);
-
- if (h > horiz && h < besth) {
- best = candidate;
- besth = h;
- }
- }
-
- int here = start;
- for (int i = 0; i < dirs.mDirections.length; i++) {
- int there = here + dirs.mDirections[i];
- if (there > end)
- there = end;
-
- float h = getPrimaryHorizontal(here);
-
- if (h > horiz && h < besth) {
- best = here;
- besth = h;
- }
-
- candidate = TextUtils.getOffsetAfter(mText, here);
- if (candidate >= start && candidate <= end) {
- h = getPrimaryHorizontal(candidate);
-
- if (h > horiz && h < besth) {
- best = candidate;
- besth = h;
- }
- }
-
- candidate = TextUtils.getOffsetBefore(mText, there);
- if (candidate >= start && candidate <= end) {
- h = getPrimaryHorizontal(candidate);
-
- if (h > horiz && h < besth) {
- best = candidate;
- besth = h;
- }
- }
-
- here = there;
- }
-
- float h = getPrimaryHorizontal(end);
-
- if (h > horiz && h < besth) {
- best = end;
- besth = h;
- }
-
- if (best != offset)
- return best;
-
- int dir = getParagraphDirection(line);
-
- if (dir > 0) {
- if (line == getLineCount() - 1)
- return best;
- else
- return getOffsetForHorizontal(line + 1, -10000);
+ if (caret == lineStart) {
+ runIndex = -2;
+ } else if (caret == lineEnd) {
+ runIndex = runs.length;
} else {
- if (line == 0)
- return best;
- else
- return getOffsetForHorizontal(line - 1, -10000);
+ // First, get information about the run containing the character with
+ // the active caret.
+ for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
+ runStart = lineStart + runs[runIndex];
+ if (caret >= runStart) {
+ runLimit = runStart + (runs[runIndex+1] & RUN_LENGTH_MASK);
+ if (runLimit > lineEnd) {
+ runLimit = lineEnd;
+ }
+ if (caret < runLimit) {
+ runLevel = (runs[runIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+ if (caret == runStart) {
+ // The caret is on a run boundary, see if we should
+ // use the position on the trailing edge of the previous
+ // logical character instead.
+ int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
+ int pos = caret - 1;
+ for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
+ prevRunStart = lineStart + runs[prevRunIndex];
+ if (pos >= prevRunStart) {
+ prevRunLimit = prevRunStart + (runs[prevRunIndex+1] & RUN_LENGTH_MASK);
+ if (prevRunLimit > lineEnd) {
+ prevRunLimit = lineEnd;
+ }
+ if (pos < prevRunLimit) {
+ prevRunLevel = (runs[prevRunIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+ if (prevRunLevel < runLevel) {
+ // Start from logically previous character.
+ runIndex = prevRunIndex;
+ runLevel = prevRunLevel;
+ runStart = prevRunStart;
+ runLimit = prevRunLimit;
+ trailing = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ // caret might be = lineEnd. This is generally a space or paragraph
+ // separator and has an associated run, but might be the end of
+ // text, in which case it doesn't. If that happens, we ran off the
+ // end of the run list, and runIndex == runs.length. In this case,
+ // we are at a run boundary so we skip the below test.
+ if (runIndex != runs.length) {
+ boolean rtlRun = (runLevel & 0x1) != 0;
+ boolean advance = toLeft == rtlRun;
+ if (caret != (advance ? runLimit : runStart) || advance != trailing) {
+ // Moving within or into the run, so we can move logically.
+ newCaret = getOffsetBeforeAfter(caret, advance);
+ // If the new position is internal to the run, we're at the strong
+ // position already so we're finished.
+ if (newCaret != (advance ? runLimit : runStart)) {
+ return newCaret;
+ }
+ }
+ }
}
+
+ // If newCaret is -1, we're starting at a run boundary and crossing
+ // into another run. Otherwise we've arrived at a run boundary, and
+ // need to figure out which character to attach to. Note we might
+ // need to run this twice, if we cross a run boundary and end up at
+ // another run boundary.
+ while (true) {
+ boolean advance = toLeft == paraIsRtl;
+ int otherRunIndex = runIndex + (advance ? 2 : -2);
+ if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
+ int otherRunStart = lineStart + runs[otherRunIndex];
+ int otherRunLimit = otherRunStart + (runs[otherRunIndex+1] & RUN_LENGTH_MASK);
+ if (otherRunLimit > lineEnd) {
+ otherRunLimit = lineEnd;
+ }
+ int otherRunLevel = runs[otherRunIndex+1] >>> RUN_LEVEL_SHIFT & RUN_LEVEL_MASK;
+ boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
+
+ advance = toLeft == otherRunIsRtl;
+ if (newCaret == -1) {
+ newCaret = getOffsetBeforeAfter(advance ? otherRunStart : otherRunLimit, advance);
+ if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
+ // Crossed and ended up at a new boundary, repeat a second and final time.
+ runIndex = otherRunIndex;
+ runLevel = otherRunLevel;
+ continue;
+ }
+ break;
+ }
+
+ // The new caret is at a boundary.
+ if (otherRunLevel < runLevel) {
+ // The strong character is in the other run.
+ newCaret = advance ? otherRunStart : otherRunLimit;
+ }
+ break;
+ }
+
+ if (newCaret == -1) {
+ // We're walking off the end of the line. The paragraph
+ // level is always equal to or lower than any internal level, so
+ // the boundaries get the strong caret.
+ newCaret = getOffsetBeforeAfter(caret, advance);
+ break;
+ }
+ // Else we've arrived at the end of the line. That's a strong position.
+ // We might have arrived here by crossing over a run with no internal
+ // breaks and dropping out of the above loop before advancing one final
+ // time, so reset the caret.
+ // Note, we use '<=' below to handle a situation where the only run
+ // on the line is a counter-directional run. If we're not advancing,
+ // we can end up at the 'lineEnd' position but the caret we want is at
+ // the lineStart.
+ if (newCaret <= lineEnd) {
+ newCaret = advance ? lineEnd : lineStart;
+ }
+ break;
+ }
+
+ return newCaret;
+ }
+
+ // utility, maybe just roll into the above.
+ private int getOffsetBeforeAfter(int offset, boolean after) {
+ if (after) {
+ return TextUtils.getOffsetAfter(mText, offset);
+ }
+ return TextUtils.getOffsetBefore(mText, offset);
}
private int getOffsetAtStartOf(int offset) {
+ // XXX this probably should skip local reorderings and
+ // zero-width characters, look at callers
if (offset == 0)
return 0;
@@ -1115,7 +1171,7 @@
/**
* Fills in the specified Path with a representation of a cursor
* at the specified offset. This will often be a vertical line
- * but can be multiple discontinous lines in text with multiple
+ * but can be multiple discontinuous lines in text with multiple
* directionalities.
*/
public void getCursorPath(int point, Path dest,
@@ -1127,7 +1183,8 @@
int bottom = getLineTop(line+1);
float h1 = getPrimaryHorizontal(point) - 0.5f;
- float h2 = getSecondaryHorizontal(point) - 0.5f;
+ float h2 = isLevelBoundary(point) ?
+ getSecondaryHorizontal(point) - 0.5f : h1;
int caps = TextKeyListener.getMetaState(editingBuffer,
KeyEvent.META_SHIFT_ON) |
@@ -1204,9 +1261,10 @@
if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
lineend--;
- int here = linestart;
- for (int i = 0; i < dirs.mDirections.length; i++) {
- int there = here + dirs.mDirections[i];
+ for (int i = 0; i < dirs.mDirections.length; i += 2) {
+ int here = linestart + dirs.mDirections[i];
+ int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+
if (there > lineend)
there = lineend;
@@ -1215,14 +1273,12 @@
int en = Math.min(end, there);
if (st != en) {
- float h1 = getHorizontal(st, false, false, line);
- float h2 = getHorizontal(en, true, false, line);
+ float h1 = getHorizontal(st, false, line);
+ float h2 = getHorizontal(en, true, line);
dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
}
}
-
- here = there;
}
}
@@ -1257,7 +1313,7 @@
addSelection(startline, start, getLineEnd(startline),
top, getLineBottom(startline), dest);
-
+
if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
dest.addRect(getLineLeft(startline), top,
0, getLineBottom(startline), Path.Direction.CW);
@@ -1395,27 +1451,31 @@
float h = 0;
- int here = 0;
- for (int i = 0; i < directions.mDirections.length; i++) {
- int there = here + directions.mDirections[i];
- if (there > end - start)
- there = end - start;
+ int lastRunIndex = directions.mDirections.length - 2;
+ for (int i = 0; i < directions.mDirections.length; i += 2) {
+ int here = start + directions.mDirections[i];
+ int there = here + (directions.mDirections[i+1] & RUN_LENGTH_MASK);
+ boolean runIsRtl = (directions.mDirections[i+1] & RUN_RTL_FLAG) != 0;
+
+ if (there > end)
+ there = end;
int segstart = here;
for (int j = hasTabs ? here : there; j <= there; j++) {
- if (j == there || buf[j] == '\t') {
+ int pj = j - start;
+ if (j == there || buf[pj] == '\t') {
h += Styled.drawText(canvas, text,
- start + segstart, start + j,
- dir, (i & 1) != 0, x + h,
+ segstart, j,
+ dir, runIsRtl, x + h,
top, y, bottom, paint, workPaint,
- start + j != end);
+ i != lastRunIndex || j != end);
- if (j != there && buf[j] == '\t')
+ if (j != there)
h = dir * nextTab(text, start, end, h * dir, parspans);
segstart = j + 1;
- } else if (hasTabs && buf[j] >= 0xD800 && buf[j] <= 0xDFFF && j + 1 < there) {
- int emoji = Character.codePointAt(buf, j);
+ } else if (hasTabs && buf[pj] >= 0xD800 && buf[pj] <= 0xDFFF && j + 1 < there) {
+ int emoji = Character.codePointAt(buf, pj);
if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Bitmap bm = EMOJI_FACTORY.
@@ -1423,10 +1483,10 @@
if (bm != null) {
h += Styled.drawText(canvas, text,
- start + segstart, start + j,
- dir, (i & 1) != 0, x + h,
+ segstart, j,
+ dir, runIsRtl, x + h,
top, y, bottom, paint, workPaint,
- start + j != end);
+ i != lastRunIndex || j != end);
if (mEmojiRect == null) {
mEmojiRect = new RectF();
@@ -1434,9 +1494,9 @@
workPaint.set(paint);
Styled.measureText(paint, workPaint, text,
- start + j, start + j + 1,
+ j, j + 1,
null);
-
+
float bitmapHeight = bm.getHeight();
float textHeight = -workPaint.ascent();
float scale = textHeight / bitmapHeight;
@@ -1454,21 +1514,24 @@
}
}
}
-
- here = there;
}
if (hasTabs)
TextUtils.recycle(buf);
}
- private static float measureText(TextPaint paint,
+ /**
+ * Get the distance from the margin to the requested edge of the character
+ * at offset on the line from start to end. Trailing indicates the edge
+ * should be the trailing edge of the character at offset-1.
+ */
+ /* package */ static float measureText(TextPaint paint,
TextPaint workPaint,
CharSequence text,
int start, int offset, int end,
int dir, Directions directions,
- boolean trailing, boolean alt,
- boolean hasTabs, Object[] tabs) {
+ boolean trailing, boolean hasTabs,
+ Object[] tabs) {
char[] buf = null;
if (hasTabs) {
@@ -1478,19 +1541,19 @@
float h = 0;
- if (alt) {
- if (dir == DIR_RIGHT_TO_LEFT)
- trailing = !trailing;
+ int target = trailing ? offset - 1 : offset;
+ if (target < start) {
+ return 0;
}
- int here = 0;
- for (int i = 0; i < directions.mDirections.length; i++) {
- if (alt)
- trailing = !trailing;
-
- int there = here + directions.mDirections[i];
- if (there > end - start)
- there = end - start;
+ int[] runs = directions.mDirections;
+ for (int i = 0; i < runs.length; i += 2) {
+ int here = start + runs[i];
+ int there = here + (runs[i+1] & RUN_LENGTH_MASK);
+ if (there > end) {
+ there = end;
+ }
+ boolean runIsRtl = (runs[i+1] & RUN_RTL_FLAG) != 0;
int segstart = here;
for (int j = hasTabs ? here : there; j <= there; j++) {
@@ -1498,11 +1561,11 @@
Bitmap bm = null;
if (hasTabs && j < there) {
- codept = buf[j];
+ codept = buf[j - start];
}
if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
- codept = Character.codePointAt(buf, j);
+ codept = Character.codePointAt(buf, j - start);
if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
@@ -1512,33 +1575,34 @@
if (j == there || codept == '\t' || bm != null) {
float segw;
- if (offset < start + j ||
- (trailing && offset <= start + j)) {
- if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) {
+ boolean inSegment = target >= segstart && target < j;
+
+ if (inSegment) {
+ if (dir == DIR_LEFT_TO_RIGHT && !runIsRtl) {
h += Styled.measureText(paint, workPaint, text,
- start + segstart, offset,
+ segstart, offset,
null);
return h;
}
- if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) {
+ if (dir == DIR_RIGHT_TO_LEFT && runIsRtl) {
h -= Styled.measureText(paint, workPaint, text,
- start + segstart, offset,
+ segstart, offset,
null);
return h;
}
}
+ // XXX Style.measureText assumes LTR?
segw = Styled.measureText(paint, workPaint, text,
- start + segstart, start + j,
+ segstart, j,
null);
- if (offset < start + j ||
- (trailing && offset <= start + j)) {
+ if (inSegment) {
if (dir == DIR_LEFT_TO_RIGHT) {
h += segw - Styled.measureText(paint, workPaint,
text,
- start + segstart,
+ segstart,
offset, null);
return h;
}
@@ -1546,7 +1610,7 @@
if (dir == DIR_RIGHT_TO_LEFT) {
h -= segw - Styled.measureText(paint, workPaint,
text,
- start + segstart,
+ segstart,
offset, null);
return h;
}
@@ -1557,11 +1621,15 @@
else
h += segw;
- if (j != there && buf[j] == '\t') {
- if (offset == start + j)
+ if (j != there && buf[j - start] == '\t') {
+ if (offset == j)
return h;
h = dir * nextTab(text, start, end, h * dir, tabs);
+
+ if (target == j) {
+ return h;
+ }
}
if (bm != null) {
@@ -1569,7 +1637,7 @@
Styled.measureText(paint, workPaint, text,
j, j + 2, null);
- float wid = (float) bm.getWidth() *
+ float wid = bm.getWidth() *
-workPaint.ascent() / bm.getHeight();
if (dir == DIR_RIGHT_TO_LEFT) {
@@ -1584,8 +1652,6 @@
segstart = j + 1;
}
}
-
- here = there;
}
if (hasTabs)
@@ -1616,7 +1682,7 @@
Paint.FontMetricsInt fm,
boolean hasTabs, Object[] tabs) {
char[] buf = null;
-
+
if (hasTabs) {
buf = TextUtils.obtain(end - start);
TextUtils.getChars(text, start, end, buf, 0);
@@ -1652,6 +1718,8 @@
if (pos == len || codept == '\t' || bm != null) {
workPaint.baselineShift = 0;
+ // XXX Styled.measureText assumes the run direction is LTR,
+ // but it might not be. Check this.
width += Styled.measureText(paint, workPaint, text,
start + lastPos, start + pos,
fm);
@@ -1683,7 +1751,7 @@
Styled.measureText(paint, workPaint, text,
start + pos, start + pos + 1, null);
- width += (float) bm.getWidth() *
+ width += bm.getWidth() *
-workPaint.ascent() / bm.getHeight();
// Since we had an emoji, we bump past the second half
@@ -1804,23 +1872,22 @@
/**
* Stores information about bidirectional (left-to-right or right-to-left)
- * text within the layout of a line. TODO: This work is not complete
- * or correct and will be fleshed out in a later revision.
+ * text within the layout of a line.
*/
public static class Directions {
- private short[] mDirections;
+ // Directions represents directional runs within a line of text.
+ // Runs are pairs of ints listed in visual order, starting from the
+ // leading margin. The first int of each pair is the offset from
+ // the first character of the line to the start of the run. The
+ // second int represents both the length and level of the run.
+ // The length is in the lower bits, accessed by masking with
+ // DIR_LENGTH_MASK. The level is in the higher bits, accessed
+ // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
+ // To simply test for an RTL direction, test the bit using
+ // DIR_RTL_FLAG, if set then the direction is rtl.
- // The values in mDirections are the offsets from the first character
- // in the line to the next flip in direction. Runs at even indices
- // are left-to-right, the others are right-to-left. So, for example,
- // a line that starts with a right-to-left run has 0 at mDirections[0],
- // since the 'first' (ltr) run is zero length.
- //
- // The code currently assumes that each run is adjacent to the previous
- // one, progressing in the base line direction. This isn't sufficient
- // to handle nested runs, for example numeric text in an rtl context
- // in an ltr paragraph.
- /* package */ Directions(short[] dirs) {
+ /* package */ int[] mDirections;
+ /* package */ Directions(int[] dirs) {
mDirections = dirs;
}
}
@@ -1870,7 +1937,7 @@
public int length() {
return mText.length();
}
-
+
public CharSequence subSequence(int start, int end) {
char[] s = new char[end - start];
getChars(start, end, s, 0);
@@ -1936,12 +2003,17 @@
public static final int DIR_LEFT_TO_RIGHT = 1;
public static final int DIR_RIGHT_TO_LEFT = -1;
-
+
/* package */ static final int DIR_REQUEST_LTR = 1;
/* package */ static final int DIR_REQUEST_RTL = -1;
/* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
/* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
+ /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
+ /* package */ static final int RUN_LEVEL_SHIFT = 26;
+ /* package */ static final int RUN_LEVEL_MASK = 0x3f;
+ /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
+
public enum Alignment {
ALIGN_NORMAL,
ALIGN_OPPOSITE,
@@ -1953,9 +2025,8 @@
private static final int TAB_INCREMENT = 20;
/* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
- new Directions(new short[] { 32767 });
+ new Directions(new int[] { 0, RUN_LENGTH_MASK });
/* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
- new Directions(new short[] { 0, 32767 });
-
+ new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f02ad2a..bfa0ab6 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,14 +16,15 @@
package android.text;
+import com.android.internal.util.ArrayUtils;
+
import android.graphics.Bitmap;
import android.graphics.Paint;
-import com.android.internal.util.ArrayUtils;
-import android.util.Log;
import android.text.style.LeadingMarginSpan;
import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
+import android.util.Log;
/**
* StaticLayout is a Layout for text that will not be edited after it
@@ -31,8 +32,9 @@
* <p>This is used by widgets to control text layout. You should not need
* to use this class directly unless you are implementing your own widget
* or custom display object, or would be tempted to call
- * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
- * Canvas.drawText()} directly.</p>
+ * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
+ * float, float, android.graphics.Paint)
+ * Canvas.drawText()} directly.</p>
*/
public class
StaticLayout
@@ -62,7 +64,7 @@
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
super((ellipsize == null)
- ? source
+ ? source
: (source instanceof Spanned)
? new SpannedEllipsizer(source)
: new Ellipsizer(source),
@@ -72,7 +74,7 @@
* This is annoying, but we can't refer to the layout until
* superclass construction is finished, and the superclass
* constructor wants the reference to the display text.
- *
+ *
* This will break if the superclass constructor ever actually
* cares about the content instead of just holding the reference.
*/
@@ -196,7 +198,7 @@
// starts in this layout, before the
// current paragraph
- choosehtv[i] = getLineTop(getLineForOffset(o));
+ choosehtv[i] = getLineTop(getLineForOffset(o));
} else {
// starts in this paragraph
@@ -224,7 +226,7 @@
boolean easy = true;
boolean altered = false;
- int dir = DEFAULT_DIR; // XXX
+ int dir = DEFAULT_DIR; // XXX pass value in
for (int i = 0; i < n; i++) {
if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
@@ -253,7 +255,8 @@
if (!easy) {
// XXX put override flags, etc. into chdirs
- dir = bidi(dir, chs, chdirs, n, false);
+ // XXX supply dir rather than force
+ dir = AndroidBidi.bidi(DIR_REQUEST_DEFAULT_LTR, chs, chdirs, n, false);
// Do mirroring for right-to-left segments
@@ -316,7 +319,7 @@
paint.getTextWidths(sub, i, next, widths);
System.arraycopy(widths, 0, widths,
end - start + (i - start), next - i);
-
+
paint.getFontMetricsInt(fm);
} else {
mWorkPaint.baselineShift = 0;
@@ -376,7 +379,7 @@
whichPaint = mWorkPaint;
}
- float wid = (float) bm.getWidth() *
+ float wid = bm.getWidth() *
-whichPaint.ascent() /
bm.getHeight();
@@ -411,7 +414,7 @@
/*
* From the Unicode Line Breaking Algorithm:
* (at least approximately)
- *
+ *
* .,:; are class IS: breakpoints
* except when adjacent to digits
* / is class SY: a breakpoint
@@ -606,239 +609,6 @@
}
}
- /**
- * Runs the unicode bidi algorithm on the first n chars in chs, returning
- * the char dirs in chInfo and the base line direction of the first
- * paragraph.
- *
- * XXX change result from dirs to levels
- *
- * @param dir the direction flag, either DIR_REQUEST_LTR,
- * DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL.
- * @param chs the text to examine
- * @param chInfo on input, if hasInfo is true, override and other flags
- * representing out-of-band embedding information. On output, the generated
- * dirs of the text.
- * @param n the length of the text/information in chs and chInfo
- * @param hasInfo true if chInfo has input information, otherwise the
- * input data in chInfo is ignored.
- * @return the resolved direction level of the first paragraph, either
- * DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT.
- */
- /* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n,
- boolean hasInfo) {
-
- AndroidCharacter.getDirectionalities(chs, chInfo, n);
-
- /*
- * Determine primary paragraph direction if not specified
- */
- if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
- // set up default
- dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
- for (int j = 0; j < n; j++) {
- int d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
- dir = DIR_LEFT_TO_RIGHT;
- break;
- }
- if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- dir = DIR_RIGHT_TO_LEFT;
- break;
- }
- }
- }
-
- final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
- Character.DIRECTIONALITY_LEFT_TO_RIGHT :
- Character.DIRECTIONALITY_RIGHT_TO_LEFT;
-
- /*
- * XXX Explicit overrides should go here
- */
-
- /*
- * Weak type resolution
- */
-
- // dump(chdirs, n, "initial");
-
- // W1 non spacing marks
- for (int j = 0; j < n; j++) {
- if (chInfo[j] == Character.NON_SPACING_MARK) {
- if (j == 0)
- chInfo[j] = SOR;
- else
- chInfo[j] = chInfo[j - 1];
- }
- }
-
- // dump(chdirs, n, "W1");
-
- // W2 european numbers
- byte cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- cur = d;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
- if (cur ==
- Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
- }
- }
-
- // dump(chdirs, n, "W2");
-
- // W3 arabic letters
- for (int j = 0; j < n; j++) {
- if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- }
-
- // dump(chdirs, n, "W3");
-
- // W4 single separator between numbers
- for (int j = 1; j < n - 1; j++) {
- byte d = chInfo[j];
- byte prev = chInfo[j - 1];
- byte next = chInfo[j + 1];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
- if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
- next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
- if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
- next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
- next == Character.DIRECTIONALITY_ARABIC_NUMBER)
- chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
- }
- }
-
- // dump(chdirs, n, "W4");
-
- // W5 european number terminators
- boolean adjacent = false;
- for (int j = 0; j < n; j++) {
- byte d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- adjacent = true;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
- chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- else
- adjacent = false;
- }
-
- //dump(chdirs, n, "W5");
-
- // W5 european number terminators part 2,
- // W6 separators and terminators
- adjacent = false;
- for (int j = n - 1; j >= 0; j--) {
- byte d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- adjacent = true;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
- if (adjacent)
- chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- else
- chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
- }
- else {
- adjacent = false;
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
- d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
- d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
- d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
- chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
- }
- }
-
- // dump(chdirs, n, "W6");
-
- // W7 strong direction of european numbers
- cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chInfo[j];
-
- if (d == SOR ||
- d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
- cur = d;
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chInfo[j] = cur;
- }
-
- // dump(chdirs, n, "W7");
-
- // N1, N2 neutrals
- cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chInfo[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- cur = d;
- } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
- d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
- cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- } else {
- byte dd = SOR;
- int k;
-
- for (k = j + 1; k < n; k++) {
- dd = chInfo[k];
-
- if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- break;
- }
- if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
- dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
- dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- break;
- }
- }
-
- for (int y = j; y < k; y++) {
- if (dd == cur)
- chInfo[y] = cur;
- else
- chInfo[y] = SOR;
- }
-
- j = k - 1;
- }
- }
-
- // dump(chdirs, n, "final");
-
- // extra: enforce that all tabs and surrogate characters go the
- // primary direction
- // TODO: actually do directions right for surrogates
-
- for (int j = 0; j < n; j++) {
- char c = chs[j];
-
- if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
- chInfo[j] = SOR;
- }
- }
-
- return dir;
- }
-
private static final char FIRST_CJK = '\u2E80';
/**
* Returns true if the specified character is one of those specified
@@ -1062,49 +832,123 @@
if (tab)
lines[off + TAB] |= TAB_MASK;
- {
- lines[off + DIR] |= dir << DIR_SHIFT;
-
- int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
- int count = 0;
-
- if (!easy) {
- for (int k = start; k < end; k++) {
- if (chdirs[k - pstart] != cur) {
- count++;
- cur = chdirs[k - pstart];
- }
+ lines[off + DIR] |= dir << DIR_SHIFT;
+ Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
+ // easy means all chars < the first RTL, so no emoji, no nothing
+ // XXX a run with no text or all spaces is easy but might be an empty
+ // RTL paragraph. Make sure easy is false if this is the case.
+ if (easy) {
+ mLineDirections[j] = linedirs;
+ } else {
+ int startOff = start - pstart;
+ int baseLevel = dir == DIR_LEFT_TO_RIGHT ? 0 : 1;
+ int curLevel = chdirs[startOff];
+ int minLevel = curLevel;
+ int runCount = 1;
+ for (int i = start + 1; i < end; ++i) {
+ int level = chdirs[i - pstart];
+ if (level != curLevel) {
+ curLevel = level;
+ ++runCount;
}
}
- Directions linedirs;
+ // add final run for trailing counter-directional whitespace
+ int visEnd = end;
+ if ((curLevel & 1) != (baseLevel & 1)) {
+ // look for visible end
+ while (--visEnd >= start) {
+ char ch = text.charAt(visEnd);
- if (count == 0) {
- linedirs = DIRS_ALL_LEFT_TO_RIGHT;
+ if (ch == '\n') {
+ --visEnd;
+ break;
+ }
+
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ }
+ ++visEnd;
+ if (visEnd != end) {
+ ++runCount;
+ }
+ }
+
+ if (runCount == 1 && minLevel == baseLevel) {
+ if ((minLevel & 1) != 0) {
+ linedirs = DIRS_ALL_RIGHT_TO_LEFT;
+ }
+ // we're done, only one run on this line
} else {
- short[] ld = new short[count + 1];
-
- cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
- count = 0;
- int here = start;
-
- for (int k = start; k < end; k++) {
- if (chdirs[k - pstart] != cur) {
- // XXX check to make sure we don't
- // overflow short
- ld[count++] = (short) (k - here);
- cur = chdirs[k - pstart];
- here = k;
+ int[] ld = new int[runCount * 2];
+ int maxLevel = minLevel;
+ int levelBits = minLevel << RUN_LEVEL_SHIFT;
+ {
+ // Start of first pair is always 0, we write
+ // length then start at each new run, and the
+ // last run length after we're done.
+ int n = 1;
+ int prev = start;
+ curLevel = minLevel;
+ for (int i = start; i < visEnd; ++i) {
+ int level = chdirs[i - pstart];
+ if (level != curLevel) {
+ curLevel = level;
+ if (level > maxLevel) {
+ maxLevel = level;
+ } else if (level < minLevel) {
+ minLevel = level;
+ }
+ // XXX ignore run length limit of 2^RUN_LEVEL_SHIFT
+ ld[n++] = (i - prev) | levelBits;
+ ld[n++] = i - start;
+ levelBits = curLevel << RUN_LEVEL_SHIFT;
+ prev = i;
+ }
+ }
+ ld[n] = (visEnd - prev) | levelBits;
+ if (visEnd < end) {
+ ld[++n] = visEnd - start;
+ ld[++n] = (end - visEnd) | (baseLevel << RUN_LEVEL_SHIFT);
}
}
- ld[count] = (short) (end - here);
-
- if (count == 1 && ld[0] == 0) {
- linedirs = DIRS_ALL_RIGHT_TO_LEFT;
+ // See if we need to swap any runs.
+ // If the min level run direction doesn't match the base
+ // direction, we always need to swap (at this point
+ // we have more than one run).
+ // Otherwise, we don't need to swap the lowest level.
+ // Since there are no logically adjacent runs at the same
+ // level, if the max level is the same as the (new) min
+ // level, we have a series of alternating levels that
+ // is already in order, so there's no more to do.
+ //
+ boolean swap;
+ if ((minLevel & 1) == baseLevel) {
+ minLevel += 1;
+ swap = maxLevel > minLevel;
} else {
- linedirs = new Directions(ld);
+ swap = runCount > 1;
}
+ if (swap) {
+ for (int level = maxLevel - 1; level >= minLevel; --level) {
+ for (int i = 0; i < ld.length; i += 2) {
+ if (chdirs[startOff + ld[i]] >= level) {
+ int e = i + 2;
+ while (e < ld.length && chdirs[startOff + ld[e]] >= level) {
+ e += 2;
+ }
+ for (int low = i, hi = e - 2; low < hi; low += 2, hi -= 2) {
+ int x = ld[low]; ld[low] = ld[hi]; ld[hi] = x;
+ x = ld[low+1]; ld[low+1] = ld[hi+1]; ld[hi+1] = x;
+ }
+ i = e + 2;
+ }
+ }
+ }
+ }
+ linedirs = new Directions(ld);
}
mLineDirections[j] = linedirs;
@@ -1232,11 +1076,11 @@
}
public int getLineTop(int line) {
- return mLines[mColumns * line + TOP];
+ return mLines[mColumns * line + TOP];
}
public int getLineDescent(int line) {
- return mLines[mColumns * line + DESCENT];
+ return mLines[mColumns * line + DESCENT];
}
public int getLineStart(int line) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 48e7f79..0a39ab6 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -559,6 +559,16 @@
boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
setSmoothScrollbarEnabled(smoothScrollbar);
+
+ final int adapterId = a.getResourceId(R.styleable.AbsListView_adapter, 0);
+ if (adapterId != 0) {
+ final Context c = context;
+ post(new Runnable() {
+ public void run() {
+ setAdapter(Adapters.loadAdapter(c, adapterId));
+ }
+ });
+ }
a.recycle();
}
diff --git a/core/java/android/widget/Adapters.java b/core/java/android/widget/Adapters.java
new file mode 100644
index 0000000..05e501a
--- /dev/null
+++ b/core/java/android/widget/Adapters.java
@@ -0,0 +1,1191 @@
+/*
+ * Copyright (C) 2010 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 android.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.View;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import static com.android.internal.R.*;
+
+/**
+ * <p>This class can be used to load {@link android.widget.Adapter adapters} defined in
+ * XML resources. XML-defined adapters can be used to easily create adapters in your
+ * own application or to pass adapters to other processes.</p>
+ *
+ * <h2>Types of adapters</h2>
+ * <p>Adapters defined using XML resources can only be one of the following supported
+ * types. Arbitrary adapters are not supported to guarantee the safety of the loaded
+ * code when adapters are loaded across packages.</p>
+ * <ul>
+ * <li><a href="#xml-cursor-adapter">Cursor adapter</a>: a cursor adapter can be used
+ * to display the content of a cursor, most often coming from a content provider</li>
+ * </ul>
+ * <p>The complete XML format definition of each adapter type is available below.</p>
+ *
+ * <a name="xml-cursor-adapter" />
+ * <h2>Cursor adapter</h2>
+ * <p>A cursor adapter XML definition starts with the
+ * <a href="#xml-cursor-adapter-tag"><code><cursor-adapter /></code></a>
+ * tag and may contain one or more instances of the following tags:</p>
+ * <ul>
+ * <li><a href="#xml-cursor-adapter-select-tag"><code><select /></code></a></li>
+ * <li><a href="#xml-cursor-adapter-bind-tag"><code><bind /></code></a></li>
+ * </ul>
+ *
+ * <a name="xml-cursor-adapter-tag" />
+ * <h3><cursor-adapter /></h3>
+ * <p>The <code><cursor-adapter /></code> element defines the beginning of the
+ * document and supports the following attributes:</p>
+ * <ul>
+ * <li><code>android:layout</code>: Reference to the XML layout to be inflated for
+ * each item of the adapter. This attribute is mandatory.</li>
+ * <li><code>android:selection</code>: Selection expression, used when the
+ * <code>android:uri</code> attribute is defined or when the adapter is loaded with
+ * {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ * This attribute is optional.</li>
+ * <li><code>android:sortOrder</code>: Sort expression, used when the
+ * <code>android:uri</code> attribute is defined or when the adapter is loaded with
+ * {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ * This attribute is optional.</li>
+ * <li><code>android:uri</code>: URI of the content provider to query to retrieve a cursor.
+ * Specifying this attribute is equivalent to calling {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ * If you call this method, the value of the XML attribute is ignored. This attribute is
+ * optional.</li>
+ * </ul>
+ * <p>In addition, you can specify one or more instances of
+ * <a href="#xml-cursor-adapter-select-tag"><code><select /></code></a> and
+ * <a href="#xml-cursor-adapter-bind-tag"><code><bind /></code></a> tags as children
+ * of <code><cursor-adapter /></code>.</p>
+ *
+ * <a name="xml-cursor-adapter-select-tag" />
+ * <h3><select /></h3>
+ * <p>The <code><select /></code> tag is used to select columns from the cursor
+ * when doing the query. This can be very useful when using transformations in the
+ * <code><bind /></code> elements. It can also be very useful if you are providing
+ * your own <a href="#xml-cursor-adapter-bind-data-types">binder</a> or
+ * <a href="#xml-cursor-adapter-bind-data-types">transformation</a> classes.
+ * <code><select /></code> elements are ignored if you supply the cursor yourself.</p>
+ * <p>The <code><select /></code> supports the following attributes:</p>
+ * <ul>
+ * <li><code>android:column</code>: Name of the column to select in the cursor during the
+ * query operation</li>
+ * </ul>
+ * <p><strong>Note:</strong> The column named <code>_id</code> is always implicitely
+ * selected.</p>
+ *
+ * <a name="xml-cursor-adapter-bind-tag" />
+ * <h3><bind /></h3>
+ * <p>The <code><bind /></code> tag is used to bind a column from the cursor to
+ * a {@link android.view.View}. A column bound using this tag is automatically selected
+ * during the query and a matching
+ * <a href="#xml-cursor-adapter-select-tag"><code><select /></code> tag is therefore
+ * not required.</p>
+ *
+ * <p>Each binding is declared as a one to one matching but
+ * custom binder classes or special
+ * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> can
+ * allow you to bind several columns to a single view. In this case you must use the
+ * <a href="#xml-cursor-adapter-select-tag"><code><select /></code> tag to make
+ * sure any required column is part of the query.</p>
+ *
+ * <p>The <code><bind /></code> tag supports the following attributes:</p>
+ * <ul>
+ * <li><code>android:from</code>: The name of the column to bind from.
+ * This attribute is mandatory.</li>
+ * <li><code>android:to</code>: The id of the view to bind to. This attribute is mandatory.</li>
+ * <li><code>android:as</code>: The <a href="#xml-cursor-adapter-bind-data-types">data type</a>
+ * of the binding. This attribute is mandatory.</li>
+ * </ul>
+ *
+ * <p>In addition, a <code><bind /></code> can contain zero or more instances of
+ * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> chilren
+ * tags.</p>
+ *
+ * <a name="xml-cursor-adapter-bind-data-types" />
+ * <h4>Binding data types</h4>
+ * <p>For a binding to occur the data type of the bound column/view pair must be specified.
+ * The following data types are currently supported:</p>
+ * <ul>
+ * <li><code>string</code>: The content of the column is interpreted as a string and must be
+ * bound to a {@link android.widget.TextView}</li>
+ * <li><code>image</code>: The content of the column is interpreted as a blob describing an
+ * image and must be bound to an {@link android.widget.ImageView}</li>
+ * <li><code>image-uri</code>: The content of the column is interpreted as a URI to an image
+ * and must be bound to an {@link android.widget.ImageView}</li>
+ * <li><code>drawable</code>: The content of the column is interpreted as a resource id to a
+ * drawable and must be bound to an {@link android.widget.ImageView}</li>
+ * <li>A fully qualified class name: The name of a class corresponding to an implementation of
+ * {@link android.widget.Adapters.CursorBinder}. Cursor binders can be used to provide
+ * bindings not supported by default. Custom binders cannot be used with
+ * {@link android.content.Context#isRestricted() restricted contexts}, for instance in an
+ * app widget</li>
+ * </ul>
+ *
+ * <a name="xml-cursor-adapter-bind-transformation" />
+ * <h4>Binding transformations</h4>
+ * <p>When defining a data binding you can specify an optional transformation by using one
+ * of the following tags as a child of a <code><bind /></code> elements:</p>
+ * <ul>
+ * <li><code><map /></code>: Maps a constant string to a string or a resource. Use
+ * one instance of this tag per value you want to map</li>
+ * <li><code><transform /></code>: Transforms a column's value using an expression
+ * or an instance of {@link android.widget.Adapters.CursorTransformation}</li>
+ * </ul>
+ * <p>While several <code><map /></code> tags can be used at the same time, you cannot
+ * mix <code><map /></code> and <code><transform /></code> tags. If several
+ * <code><transform /></code> tags are specified, only the last one is retained.</p>
+ *
+ * <a name="xml-cursor-adapter-bind-transformation-map" />
+ * <p><strong><map /></strong></p>
+ * <p>A map element simply specifies a value to match from and a value to match to. When
+ * a column's value equals the value to match from, it is replaced with the value to match
+ * to. The following attributes are supported:</p>
+ * <ul>
+ * <li><code>android:fromValue</code>: The value to match from. This attribute is mandatory</li>
+ * <li><code>android:toValue</code>: The value to match to. This value can be either a string
+ * or a resource identifier. This value is interpreted as a resource identifier when the
+ * data binding is of type <code>drawable</code>. This attribute is mandatory</li>
+ * </ul>
+ *
+ * <a name="xml-cursor-adapter-bind-transformation-transform" />
+ * <p><strong><transform /></strong></p>
+ * <p>A simple transform that occurs either by calling a specified class or by performing
+ * simple text substitution. The following attributes are supported:</p>
+ * <ul>
+ * <li><code>android:withExpression</code>: The transformation expression. The expression is
+ * a string containing column names surrounded with curly braces { and }. During the
+ * transformation each column name is replaced by its value. All columns must have been
+ * selected in the query. An example of expression is <code>"First name: {first_name},
+ * last name: {last_name}"</code>. This attribute is mandatory
+ * if <code>android:withClass</code> is not specified and ignored if <code>android:withClass</code>
+ * is specified</li>
+ * <li><code>android:withClass</code>: A fully qualified class name corresponding to an
+ * implementation of {@link android.widget.Adapters.CursorTransformation}. Custom
+ * transformationscannot be used with
+ * {@link android.content.Context#isRestricted() restricted contexts}, for instance in
+ * an app widget This attribute is mandatory if <code>android:withExpression</code> is
+ * not specified</li>
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * <p>The following example defines a cursor adapter that queries all the contacts with
+ * a phone number using the contacts content provider. Each contact is displayed with
+ * its display name, its favorite status and its photo. To display photos, a custom data
+ * binder is declared:</p>
+ *
+ * <pre class="prettyprint">
+ * <cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:uri="content://com.android.contacts/contacts"
+ * android:selection="has_phone_number=1"
+ * android:layout="@layout/contact_item">
+ *
+ * <bind android:from="display_name" android:to="@id/name" android:as="string" />
+ * <bind android:from="starred" android:to="@id/star" android:as="drawable">
+ * <map android:fromValue="0" android:toValue="@android:drawable/star_big_off" />
+ * <map android:fromValue="1" android:toValue="@android:drawable/star_big_on" />
+ * </bind>
+ * <bind android:from="_id" android:to="@id/name"
+ * android:as="com.google.android.test.adapters.ContactPhotoBinder" />
+ *
+ * </cursor-adapter>
+ * </pre>
+ *
+ * <h3>Related APIs</h3>
+ * <ul>
+ * <li>{@link android.widget.Adapters#loadAdapter(android.content.Context, int, Object[])}</li>
+ * <li>{@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, android.database.Cursor, Object[])}</li>
+ * <li>{@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}</li>
+ * <li>{@link android.widget.Adapters.CursorBinder}</li>
+ * <li>{@link android.widget.Adapters.CursorTransformation}</li>
+ * <li>{@link android.widget.CursorAdapter}</li>
+ * </ul>
+ *
+ * @see android.widget.Adapter
+ * @see android.content.ContentProvider
+ *
+ * @attr ref android.R.styleable#CursorAdapter_layout
+ * @attr ref android.R.styleable#CursorAdapter_selection
+ * @attr ref android.R.styleable#CursorAdapter_sortOrder
+ * @attr ref android.R.styleable#CursorAdapter_uri
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_as
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_from
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_to
+ * @attr ref android.R.styleable#CursorAdapter_MapItem_fromValue
+ * @attr ref android.R.styleable#CursorAdapter_MapItem_toValue
+ * @attr ref android.R.styleable#CursorAdapter_SelectItem_column
+ * @attr ref android.R.styleable#CursorAdapter_TransformItem_withClass
+ * @attr ref android.R.styleable#CursorAdapter_TransformItem_withExpression
+ */
+@SuppressWarnings({"JavadocReference"})
+public class Adapters {
+ private static final String ADAPTER_CURSOR = "cursor-adapter";
+
+ /**
+ * <p>Interface used to bind a {@link android.database.Cursor} column to a View. This
+ * interface can be used to provide bindings for data types not supported by the
+ * standard implementation of {@link android.widget.Adapters}.</p>
+ *
+ * <p>A binder is provided with a cursor transformation which may or may not be used
+ * to transform the value retrieved from the cursor. The transformation is guaranteed
+ * to never be null so it's always safe to apply the transformation.</p>
+ *
+ * <p>The binder is associated with a Context but can be re-used with multiple cursors.
+ * As such, the implementation should make no assumption about the Cursor in use.</p>
+ *
+ * @see android.view.View
+ * @see android.database.Cursor
+ * @see android.widget.Adapters.CursorTransformation
+ */
+ public static abstract class CursorBinder {
+ /**
+ * <p>The context associated with this binder.</p>
+ */
+ protected final Context mContext;
+
+ /**
+ * <p>The transformation associated with this binder. This transformation is never
+ * null and may or may not be applied to the Cursor data during the
+ * {@link #bind(android.view.View, android.database.Cursor, int)} operation.</p>
+ *
+ * @see #bind(android.view.View, android.database.Cursor, int)
+ */
+ protected final CursorTransformation mTransformation;
+
+ /**
+ * <p>Creates a new Cursor binder.</p>
+ *
+ * @param context The context associated with this binder.
+ * @param transformation The transformation associated with this binder. This
+ * transformation may or may not be applied by the binder and is guaranteed
+ * to not be null.
+ */
+ public CursorBinder(Context context, CursorTransformation transformation) {
+ mContext = context;
+ mTransformation = transformation;
+ }
+
+ /**
+ * <p>Binds the specified Cursor column to the supplied View. The binding operation
+ * can query other Cursor columns as needed. During the binding operation, values
+ * retrieved from the Cursor may or may not be transformed using this binder's
+ * cursor transformation.</p>
+ *
+ * @param view The view to bind data to.
+ * @param cursor The cursor to bind data from.
+ * @param columnIndex The column index in the cursor where the data to bind resides.
+ *
+ * @see #mTransformation
+ *
+ * @return True if the column was successfully bound to the View, false otherwise.
+ */
+ public abstract boolean bind(View view, Cursor cursor, int columnIndex);
+ }
+
+ /**
+ * <p>Interface used to transform data coming out of a {@link android.database.Cursor}
+ * before it is bound to a {@link android.view.View}.</p>
+ *
+ * <p>Transformations are used to transform text-based data (in the form of a String),
+ * or to transform data into a resource identifier. A default implementation is provided
+ * to generate resource identifiers.</p>
+ *
+ * @see android.database.Cursor
+ * @see android.widget.Adapters.CursorBinder
+ */
+ public static abstract class CursorTransformation {
+ /**
+ * <p>The context associated with this transformation.</p>
+ */
+ protected final Context mContext;
+
+ /**
+ * <p>Creates a new Cursor transformation.</p>
+ *
+ * @param context The context associated with this transformation.
+ */
+ public CursorTransformation(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * <p>Transforms the specified Cursor column into a String. The transformation
+ * can simply return the content of the column as a String (this is known
+ * as the identity transformation) or manipulate the content. For instance,
+ * a transformation can perform text substitutions or concatenate other
+ * columns with the specified column.</p>
+ *
+ * @param cursor The cursor that contains the data to transform.
+ * @param columnIndex The index of the column to transform.
+ *
+ * @return A String containing the transformed value of the column.
+ */
+ public abstract String transform(Cursor cursor, int columnIndex);
+
+ /**
+ * <p>Transforms the specified Cursor column into a resource identifier.
+ * The default implementation simply interprets the content of the column
+ * as an integer.</p>
+ *
+ * @param cursor The cursor that contains the data to transform.
+ * @param columnIndex The index of the column to transform.
+ *
+ * @return A resource identifier.
+ */
+ public int transformToResource(Cursor cursor, int columnIndex) {
+ return cursor.getInt(columnIndex);
+ }
+ }
+
+ /**
+ * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified
+ * XML resource. The content of the adapter is loaded from the content provider
+ * identified by the supplied URI.</p>
+ *
+ * <p><strong>Note:</strong> If the supplied {@link android.content.Context} is
+ * an {@link android.app.Activity}, the cursor returned by the content provider
+ * will be automatically managed. Otherwise, you are responsible for managing the
+ * cursor yourself.</p>
+ *
+ * <p>The format of the XML definition of the cursor adapter is documented at
+ * the top of this page.</p>
+ *
+ * @param context The context to load the XML resource from.
+ * @param id The identifier of the XML resource declaring the adapter.
+ * @param uri The URI of the content provider.
+ * @param parameters Optional parameters to pass to the CursorAdapter, used
+ * to substitute values in the selection expression.
+ *
+ * @return A {@link android.widget.CursorAdapter}
+ *
+ * @throws IllegalArgumentException If the XML resource does not contain
+ * a valid <cursor-adapter /> definition.
+ *
+ * @see android.content.ContentProvider
+ * @see android.widget.CursorAdapter
+ * @see #loadAdapter(android.content.Context, int, Object[])
+ */
+ public static CursorAdapter loadCursorAdapter(Context context, int id, String uri,
+ Object... parameters) {
+
+ XmlCursorAdapter adapter = (XmlCursorAdapter) loadAdapter(context, id, ADAPTER_CURSOR,
+ parameters);
+
+ if (uri != null) {
+ adapter.setUri(uri);
+ }
+ adapter.load();
+
+ return adapter;
+ }
+
+ /**
+ * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified
+ * XML resource. The content of the adapter is loaded from the specified cursor.
+ * You are responsible for managing the supplied cursor.</p>
+ *
+ * <p>The format of the XML definition of the cursor adapter is documented at
+ * the top of this page.</p>
+ *
+ * @param context The context to load the XML resource from.
+ * @param id The identifier of the XML resource declaring the adapter.
+ * @param cursor The cursor containing the data for the adapter.
+ * @param parameters Optional parameters to pass to the CursorAdapter, used
+ * to substitute values in the selection expression.
+ *
+ * @return A {@link android.widget.CursorAdapter}
+ *
+ * @throws IllegalArgumentException If the XML resource does not contain
+ * a valid <cursor-adapter /> definition.
+ *
+ * @see android.content.ContentProvider
+ * @see android.widget.CursorAdapter
+ * @see android.database.Cursor
+ * @see #loadAdapter(android.content.Context, int, Object[])
+ */
+ public static CursorAdapter loadCursorAdapter(Context context, int id, Cursor cursor,
+ Object... parameters) {
+
+ XmlCursorAdapter adapter = (XmlCursorAdapter) loadAdapter(context, id, ADAPTER_CURSOR,
+ parameters);
+
+ if (cursor != null) {
+ adapter.changeCursor(cursor);
+ }
+
+ return adapter;
+ }
+
+ /**
+ * <p>Loads the adapter defined in the specified XML resource. The XML definition of
+ * the adapter must follow the format definition of one of the supported adapter
+ * types described at the top of this page.</p>
+ *
+ * <p><strong>Note:</strong> If the loaded adapter is a {@link android.widget.CursorAdapter}
+ * and the supplied {@link android.content.Context} is an {@link android.app.Activity},
+ * the cursor returned by the content provider will be automatically managed. Otherwise,
+ * you are responsible for managing the cursor yourself.</p>
+ *
+ * @param context The context to load the XML resource from.
+ * @param id The identifier of the XML resource declaring the adapter.
+ * @param parameters Optional parameters to pass to the adapter.
+ *
+ * @return An adapter instance.
+ *
+ * @see #loadCursorAdapter(android.content.Context, int, android.database.Cursor, Object[])
+ * @see #loadCursorAdapter(android.content.Context, int, String, Object[])
+ */
+ public static BaseAdapter loadAdapter(Context context, int id, Object... parameters) {
+ final BaseAdapter adapter = loadAdapter(context, id, null, parameters);
+ if (adapter instanceof ManagedAdapter) {
+ ((ManagedAdapter) adapter).load();
+ }
+ return adapter;
+ }
+
+ /**
+ * Loads an adapter from the specified XML resource. The optional assertName can
+ * be used to exit early if the adapter defined in the XML resource is not of the
+ * expected type.
+ *
+ * @param context The context to associate with the adapter.
+ * @param id The resource id of the XML document defining the adapter.
+ * @param assertName The mandatory name of the adapter in the XML document.
+ * Ignored if null.
+ * @param parameters Optional parameters passed to the adapter.
+ *
+ * @return An instance of {@link android.widget.BaseAdapter}.
+ */
+ private static BaseAdapter loadAdapter(Context context, int id, String assertName,
+ Object... parameters) {
+
+ XmlResourceParser parser = null;
+ try {
+ parser = context.getResources().getXml(id);
+ return createAdapterFromXml(context, parser, Xml.asAttributeSet(parser),
+ id, parameters, assertName);
+ } catch (XmlPullParserException ex) {
+ Resources.NotFoundException rnf = new Resources.NotFoundException(
+ "Can't load adapter resource ID " +
+ context.getResources().getResourceEntryName(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ Resources.NotFoundException rnf = new Resources.NotFoundException(
+ "Can't load adapter resource ID " +
+ context.getResources().getResourceEntryName(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ /**
+ * Generates an adapter using the specified XML parser. This method is responsible
+ * for choosing the type of the adapter to create based on the content of the
+ * XML parser.
+ *
+ * This method will generate an {@link IllegalArgumentException} if
+ * <code>assertName</code> is not null and does not match the root tag of the XML
+ * document.
+ */
+ private static BaseAdapter createAdapterFromXml(Context c,
+ XmlPullParser parser, AttributeSet attrs, int id, Object[] parameters,
+ String assertName) throws XmlPullParserException, IOException {
+
+ BaseAdapter adapter = null;
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) &&
+ type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ if (assertName != null && !assertName.equals(name)) {
+ throw new IllegalArgumentException("The adapter defined in " +
+ c.getResources().getResourceEntryName(id) + " must be a <" + name + " />");
+ }
+
+ if (ADAPTER_CURSOR.equals(name)) {
+ adapter = createCursorAdapter(c, parser, attrs, id, parameters);
+ } else {
+ throw new IllegalArgumentException("Unknown adapter name " + parser.getName() +
+ " in " + c.getResources().getResourceEntryName(id));
+ }
+ }
+
+ return adapter;
+
+ }
+
+ /**
+ * Creates an XmlCursorAdapter using an XmlCursorAdapterParser.
+ */
+ private static XmlCursorAdapter createCursorAdapter(Context c, XmlPullParser parser,
+ AttributeSet attrs, int id, Object[] parameters)
+ throws IOException, XmlPullParserException {
+
+ return new XmlCursorAdapterParser(c, parser, attrs, id).parse(parameters);
+ }
+
+ /**
+ * Parser that can generate XmlCursorAdapter instances. This parser is responsible for
+ * handling all the attributes and child nodes for a <cursor-adapter />.
+ */
+ private static class XmlCursorAdapterParser {
+ private static final String ADAPTER_CURSOR_BIND = "bind";
+ private static final String ADAPTER_CURSOR_SELECT = "select";
+ private static final String ADAPTER_CURSOR_AS_STRING = "string";
+ private static final String ADAPTER_CURSOR_AS_IMAGE = "image";
+ private static final String ADAPTER_CURSOR_AS_IMAGE_URI = "image-uri";
+ private static final String ADAPTER_CURSOR_AS_DRAWABLE = "drawable";
+ private static final String ADAPTER_CURSOR_MAP = "map";
+ private static final String ADAPTER_CURSOR_TRANSFORM = "transform";
+
+ private final Context mContext;
+ private final XmlPullParser mParser;
+ private final AttributeSet mAttrs;
+ private final int mId;
+
+ private final HashMap<String, CursorBinder> mBinders;
+ private final ArrayList<String> mFrom;
+ private final ArrayList<Integer> mTo;
+ private final CursorTransformation mIdentity;
+ private final Resources mResources;
+
+ public XmlCursorAdapterParser(Context c, XmlPullParser parser, AttributeSet attrs, int id) {
+ mContext = c;
+ mParser = parser;
+ mAttrs = attrs;
+ mId = id;
+
+ mResources = mContext.getResources();
+ mBinders = new HashMap<String, CursorBinder>();
+ mFrom = new ArrayList<String>();
+ mTo = new ArrayList<Integer>();
+ mIdentity = new IdentityTransformation(mContext);
+ }
+
+ public XmlCursorAdapter parse(Object[] parameters)
+ throws IOException, XmlPullParserException {
+
+ Resources resources = mResources;
+ TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter);
+
+ String uri = a.getString(styleable.CursorAdapter_uri);
+ String selection = a.getString(styleable.CursorAdapter_selection);
+ String sortOrder = a.getString(styleable.CursorAdapter_sortOrder);
+ int layout = a.getResourceId(styleable.CursorAdapter_layout, 0);
+ if (layout == 0) {
+ throw new IllegalArgumentException("The layout specified in " +
+ resources.getResourceEntryName(mId) + " does not exist");
+ }
+
+ a.recycle();
+
+ XmlPullParser parser = mParser;
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) &&
+ type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (ADAPTER_CURSOR_BIND.equals(name)) {
+ parseBindTag();
+ } else if (ADAPTER_CURSOR_SELECT.equals(name)) {
+ parseSelectTag();
+ } else {
+ throw new RuntimeException("Unknown tag name " + parser.getName() + " in " +
+ resources.getResourceEntryName(mId));
+ }
+ }
+
+ String[] fromArray = mFrom.toArray(new String[mFrom.size()]);
+ int[] toArray = new int[mTo.size()];
+ for (int i = 0; i < toArray.length; i++) {
+ toArray[i] = mTo.get(i);
+ }
+
+ String[] selectionArgs = null;
+ if (parameters != null) {
+ selectionArgs = new String[parameters.length];
+ for (int i = 0; i < selectionArgs.length; i++) {
+ selectionArgs[i] = (String) parameters[i];
+ }
+ }
+
+ return new XmlCursorAdapter(mContext, layout, uri, fromArray, toArray, selection,
+ selectionArgs, sortOrder, mBinders);
+ }
+
+ private void parseSelectTag() {
+ TypedArray a = mResources.obtainAttributes(mAttrs, styleable.CursorAdapter_SelectItem);
+
+ String fromName = a.getString(styleable.CursorAdapter_SelectItem_column);
+ if (fromName == null) {
+ throw new IllegalArgumentException("A select item in " +
+ mResources.getResourceEntryName(mId) + " does not have a 'column' attribute");
+ }
+
+ a.recycle();
+
+ mFrom.add(fromName);
+ mTo.add(View.NO_ID);
+ }
+
+ private void parseBindTag() throws IOException, XmlPullParserException {
+ Resources resources = mResources;
+ TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_BindItem);
+
+ String fromName = a.getString(styleable.CursorAdapter_BindItem_from);
+ if (fromName == null) {
+ throw new IllegalArgumentException("A bind item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'from' attribute");
+ }
+
+ int toName = a.getResourceId(styleable.CursorAdapter_BindItem_to, 0);
+ if (toName == 0) {
+ throw new IllegalArgumentException("A bind item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'to' attribute");
+ }
+
+ String asType = a.getString(styleable.CursorAdapter_BindItem_as);
+ if (asType == null) {
+ throw new IllegalArgumentException("A bind item in " +
+ resources.getResourceEntryName(mId) + " does not have an 'as' attribute");
+ }
+
+ mFrom.add(fromName);
+ mTo.add(toName);
+ mBinders.put(fromName, findBinder(asType));
+
+ a.recycle();
+ }
+
+ private CursorBinder findBinder(String type) throws IOException, XmlPullParserException {
+ final XmlPullParser parser = mParser;
+ final Context context = mContext;
+ CursorTransformation transformation = mIdentity;
+
+ int tagType;
+ int depth = parser.getDepth();
+
+ final boolean isDrawable = ADAPTER_CURSOR_AS_DRAWABLE.equals(type);
+
+ while (((tagType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && tagType != XmlPullParser.END_DOCUMENT) {
+
+ if (tagType != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (ADAPTER_CURSOR_TRANSFORM.equals(name)) {
+ transformation = findTransformation();
+ } else if (ADAPTER_CURSOR_MAP.equals(name)) {
+ if (!(transformation instanceof MapTransformation)) {
+ transformation = new MapTransformation(context);
+ }
+ findMap(((MapTransformation) transformation), isDrawable);
+ } else {
+ throw new RuntimeException("Unknown tag name " + parser.getName() + " in " +
+ context.getResources().getResourceEntryName(mId));
+ }
+ }
+
+ if (ADAPTER_CURSOR_AS_STRING.equals(type)) {
+ return new StringBinder(context, transformation);
+ } else if (ADAPTER_CURSOR_AS_IMAGE.equals(type)) {
+ return new ImageBinder(context, transformation);
+ } else if (ADAPTER_CURSOR_AS_IMAGE_URI.equals(type)) {
+ return new ImageUriBinder(context, transformation);
+ } else if (isDrawable) {
+ return new DrawableBinder(context, transformation);
+ } else {
+ return createBinder(type, transformation);
+ }
+ }
+
+ private CursorBinder createBinder(String type, CursorTransformation transformation) {
+ if (mContext.isRestricted()) return null;
+
+ try {
+ final Class<?> klass = Class.forName(type, true, mContext.getClassLoader());
+ if (CursorBinder.class.isAssignableFrom(klass)) {
+ final Constructor<?> c = klass.getDeclaredConstructor(
+ Context.class, CursorTransformation.class);
+ return (CursorBinder) c.newInstance(mContext, transformation);
+ }
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Cannot instanciate binder type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+ }
+
+ return null;
+ }
+
+ private void findMap(MapTransformation transformation, boolean drawable) {
+ Resources resources = mResources;
+
+ TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_MapItem);
+
+ String from = a.getString(styleable.CursorAdapter_MapItem_fromValue);
+ if (from == null) {
+ throw new IllegalArgumentException("A map item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'fromValue' attribute");
+ }
+
+ if (!drawable) {
+ String to = a.getString(styleable.CursorAdapter_MapItem_toValue);
+ if (to == null) {
+ throw new IllegalArgumentException("A map item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute");
+ }
+ transformation.addStringMapping(from, to);
+ } else {
+ int to = a.getResourceId(styleable.CursorAdapter_MapItem_toValue, 0);
+ if (to == 0) {
+ throw new IllegalArgumentException("A map item in " +
+ resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute");
+ }
+ transformation.addResourceMapping(from, to);
+ }
+
+ a.recycle();
+ }
+
+ private CursorTransformation findTransformation() {
+ Resources resources = mResources;
+ CursorTransformation transformation = null;
+ TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_TransformItem);
+
+ String className = a.getString(styleable.CursorAdapter_TransformItem_withClass);
+ if (className == null) {
+ String expression = a.getString(
+ styleable.CursorAdapter_TransformItem_withExpression);
+ transformation = createExpressionTransformation(expression);
+ } else if (!mContext.isRestricted()) {
+ try {
+ final Class<?> klass = Class.forName(className, true, mContext.getClassLoader());
+ if (CursorTransformation.class.isAssignableFrom(klass)) {
+ final Constructor<?> c = klass.getDeclaredConstructor(Context.class);
+ transformation = (CursorTransformation) c.newInstance(mContext);
+ }
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Cannot instanciate transform type in " +
+ mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+ }
+ }
+
+ a.recycle();
+
+ if (transformation == null) {
+ throw new IllegalArgumentException("A transform item in " +
+ resources.getResourceEntryName(mId) + " must have a 'withClass' or " +
+ "'withExpression' attribute");
+ }
+
+ return transformation;
+ }
+
+ private CursorTransformation createExpressionTransformation(String expression) {
+ return new ExpressionTransformation(mContext, expression);
+ }
+ }
+
+ /**
+ * Interface used by adapters that require to be loaded after creation.
+ */
+ private static interface ManagedAdapter {
+ /**
+ * Loads the content of the adapter, asynchronously.
+ */
+ void load();
+ }
+
+ /**
+ * Implementation of a Cursor adapter defined in XML. This class is a thin wrapper
+ * of a SimpleCursorAdapter. The main difference is the ability to handle CursorBinders.
+ */
+ private static class XmlCursorAdapter extends SimpleCursorAdapter implements ManagedAdapter {
+ private final Context mContext;
+ private String mUri;
+ private final String mSelection;
+ private final String[] mSelectionArgs;
+ private final String mSortOrder;
+ private final String[] mColumns;
+ private final CursorBinder[] mBinders;
+ private AsyncTask<Void,Void,Cursor> mLoadTask;
+
+ XmlCursorAdapter(Context context, int layout, String uri, String[] from, int[] to,
+ String selection, String[] selectionArgs, String sortOrder,
+ HashMap<String, CursorBinder> binders) {
+
+ super(context, layout, null, from, to);
+ mContext = context;
+ mUri = uri;
+ mSelection = selection;
+ mSelectionArgs = selectionArgs;
+ mSortOrder = sortOrder;
+ mColumns = new String[from.length + 1];
+ // This is mandatory in CursorAdapter
+ mColumns[0] = "_id";
+ System.arraycopy(from, 0, mColumns, 1, from.length);
+
+ CursorBinder basic = new StringBinder(context, new IdentityTransformation(context));
+ final int count = from.length;
+ mBinders = new CursorBinder[count];
+
+ for (int i = 0; i < count; i++) {
+ CursorBinder binder = binders.get(from[i]);
+ if (binder == null) binder = basic;
+ mBinders[i] = binder;
+ }
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ final int count = mTo.length;
+ final int[] from = mFrom;
+ final int[] to = mTo;
+ final CursorBinder[] binders = mBinders;
+
+ for (int i = 0; i < count; i++) {
+ final View v = view.findViewById(to[i]);
+ if (v != null) {
+ binders[i].bind(v, cursor, from[i]);
+ }
+ }
+ }
+
+ public void load() {
+ if (mUri != null) {
+ mLoadTask = new QueryTask().execute();
+ }
+ }
+
+ void setUri(String uri) {
+ mUri = uri;
+ }
+
+ @Override
+ public void changeCursor(Cursor c) {
+ if (mLoadTask != null && mLoadTask.getStatus() != QueryTask.Status.FINISHED) {
+ mLoadTask.cancel(true);
+ mLoadTask = null;
+ }
+ super.changeCursor(c);
+ }
+
+ class QueryTask extends AsyncTask<Void, Void, Cursor> {
+ @Override
+ protected Cursor doInBackground(Void... params) {
+ if (mContext instanceof Activity) {
+ return ((Activity) mContext).managedQuery(
+ Uri.parse(mUri), mColumns, mSelection, mSelectionArgs, mSortOrder);
+ } else {
+ return mContext.getContentResolver().query(
+ Uri.parse(mUri), mColumns, mSelection, mSelectionArgs, mSortOrder);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Cursor cursor) {
+ if (!isCancelled()) {
+ XmlCursorAdapter.super.changeCursor(cursor);
+ }
+ }
+ }
+ }
+
+ /**
+ * Identity transformation, returns the content of the specified column as a String,
+ * without performing any manipulation. This is used when no transformation is specified.
+ */
+ private static class IdentityTransformation extends CursorTransformation {
+ public IdentityTransformation(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String transform(Cursor cursor, int columnIndex) {
+ return cursor.getString(columnIndex);
+ }
+ }
+
+ /**
+ * An expression transformation is a simple template based replacement utility.
+ * In an expression, each segment of the form <code>{(^[}]+)}</code> is replaced
+ * with the value of the column of name $1.
+ */
+ private static class ExpressionTransformation extends CursorTransformation {
+ private final ExpressionNode mFirstNode = new ConstantExpressionNode("");
+ private final StringBuilder mBuilder = new StringBuilder();
+
+ public ExpressionTransformation(Context context, String expression) {
+ super(context);
+
+ parse(expression);
+ }
+
+ private void parse(String expression) {
+ ExpressionNode node = mFirstNode;
+ int segmentStart;
+ int count = expression.length();
+
+ for (int i = 0; i < count; i++) {
+ char c = expression.charAt(i);
+ // Start a column name segment
+ segmentStart = i;
+ if (c == '{') {
+ while (i < count && (c = expression.charAt(i)) != '}') {
+ i++;
+ }
+ // We've reached the end, but the expression didn't close
+ if (c != '}') {
+ throw new IllegalStateException("The transform expression contains a " +
+ "non-closed column name: " +
+ expression.substring(segmentStart + 1, i));
+ }
+ node.next = new ColumnExpressionNode(expression.substring(segmentStart + 1, i));
+ } else {
+ while (i < count && (c = expression.charAt(i)) != '{') {
+ i++;
+ }
+ node.next = new ConstantExpressionNode(expression.substring(segmentStart, i));
+ // Rewind if we've reached a column expression
+ if (c == '{') i--;
+ }
+ node = node.next;
+ }
+ }
+
+ @Override
+ public String transform(Cursor cursor, int columnIndex) {
+ final StringBuilder builder = mBuilder;
+ builder.delete(0, builder.length());
+
+ ExpressionNode node = mFirstNode;
+ // Skip the first node
+ while ((node = node.next) != null) {
+ builder.append(node.asString(cursor));
+ }
+
+ return builder.toString();
+ }
+
+ static abstract class ExpressionNode {
+ public ExpressionNode next;
+
+ public abstract String asString(Cursor cursor);
+ }
+
+ static class ConstantExpressionNode extends ExpressionNode {
+ private final String mConstant;
+
+ ConstantExpressionNode(String constant) {
+ mConstant = constant;
+ }
+
+ @Override
+ public String asString(Cursor cursor) {
+ return mConstant;
+ }
+ }
+
+ static class ColumnExpressionNode extends ExpressionNode {
+ private final String mColumnName;
+ private Cursor mSignature;
+ private int mColumnIndex = -1;
+
+ ColumnExpressionNode(String columnName) {
+ mColumnName = columnName;
+ }
+
+ @Override
+ public String asString(Cursor cursor) {
+ if (cursor != mSignature || mColumnIndex == -1) {
+ mColumnIndex = cursor.getColumnIndex(mColumnName);
+ mSignature = cursor;
+ }
+
+ return cursor.getString(mColumnIndex);
+ }
+ }
+ }
+
+ /**
+ * A map transformation offers a simple mapping between specified String values
+ * to Strings or integers.
+ */
+ private static class MapTransformation extends CursorTransformation {
+ private final HashMap<String, String> mStringMappings;
+ private final HashMap<String, Integer> mResourceMappings;
+
+ public MapTransformation(Context context) {
+ super(context);
+ mStringMappings = new HashMap<String, String>();
+ mResourceMappings = new HashMap<String, Integer>();
+ }
+
+ void addStringMapping(String from, String to) {
+ mStringMappings.put(from, to);
+ }
+
+ void addResourceMapping(String from, int to) {
+ mResourceMappings.put(from, to);
+ }
+
+ @Override
+ public String transform(Cursor cursor, int columnIndex) {
+ final String value = cursor.getString(columnIndex);
+ final String transformed = mStringMappings.get(value);
+ return transformed == null ? value : transformed;
+ }
+
+ @Override
+ public int transformToResource(Cursor cursor, int columnIndex) {
+ final String value = cursor.getString(columnIndex);
+ final Integer transformed = mResourceMappings.get(value);
+ try {
+ return transformed == null ? Integer.parseInt(value) : transformed;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Binds a String to a TextView.
+ */
+ private static class StringBinder extends CursorBinder {
+ public StringBinder(Context context, CursorTransformation transformation) {
+ super(context, transformation);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ ((TextView) view).setText(mTransformation.transform(cursor, columnIndex));
+ return true;
+ }
+ }
+
+ /**
+ * Binds an image blob to an ImageView.
+ */
+ private static class ImageBinder extends CursorBinder {
+ public ImageBinder(Context context, CursorTransformation transformation) {
+ super(context, transformation);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ final byte[] data = cursor.getBlob(columnIndex);
+ ((ImageView) view).setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length));
+ return true;
+ }
+ }
+
+ /**
+ * Binds an image URI to an ImageView.
+ */
+ private static class ImageUriBinder extends CursorBinder {
+ public ImageUriBinder(Context context, CursorTransformation transformation) {
+ super(context, transformation);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ ((ImageView) view).setImageURI(Uri.parse(
+ mTransformation.transform(cursor, columnIndex)));
+ return true;
+ }
+ }
+
+ /**
+ * Binds a drawable resource identifier to an ImageView.
+ */
+ private static class DrawableBinder extends CursorBinder {
+ public DrawableBinder(Context context, CursorTransformation transformation) {
+ super(context, transformation);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ final int resource = mTransformation.transformToResource(cursor, columnIndex);
+ if (resource == 0) return false;
+
+ ((ImageView) view).setImageResource(resource);
+ return true;
+ }
+ }
+}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index e15a520..8611901 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -913,10 +913,10 @@
if (mItemClickListener != null) {
final DropDownListView list = mDropDownList;
- // Note that we don't have a View here, so we will need to
- // supply null. Hopefully no existing apps crash...
- mItemClickListener.onItemClick(list, null, completion.getPosition(),
- completion.getId());
+ final int position = completion.getPosition();
+ mItemClickListener.onItemClick(list,
+ list.getChildAt(position - list.getFirstVisiblePosition()),
+ position, completion.getId());
}
}
}
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index baa6833..6a891fc 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -80,6 +80,18 @@
protected FilterQueryProvider mFilterQueryProvider;
/**
+ * If set the adapter will call requery() on the cursor whenever a content change
+ * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}
+ */
+ public static final int FLAG_AUTO_REQUERY = 0x01;
+
+ /**
+ * If set the adapter will register a content observer on the cursor and will call
+ * {@link #onContentChanged()} when a notification comes in.
+ */
+ public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
+
+ /**
* Constructor. The adapter will call requery() on the cursor whenever
* it changes so that the most recent data is always displayed.
*
@@ -87,7 +99,7 @@
* @param context The context
*/
public CursorAdapter(Context context, Cursor c) {
- init(context, c, true);
+ init(context, c, FLAG_AUTO_REQUERY);
}
/**
@@ -99,19 +111,43 @@
* data is always displayed.
*/
public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
- init(context, c, autoRequery);
+ init(context, c, autoRequery ? FLAG_AUTO_REQUERY : 0);
+ }
+
+ /**
+ * Constructor
+ * @param c The cursor from which to get the data.
+ * @param context The context
+ * @param flags flags used to determine the behavior of the adapter
+ */
+ public CursorAdapter(Context context, Cursor c, int flags) {
+ init(context, c, flags);
}
protected void init(Context context, Cursor c, boolean autoRequery) {
+ init(context, c, autoRequery ? FLAG_AUTO_REQUERY : 0);
+ }
+
+ protected void init(Context context, Cursor c, int flags) {
+ if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
+ flags |= FLAG_REGISTER_CONTENT_OBSERVER;
+ mAutoRequery = true;
+ } else {
+ mAutoRequery = false;
+ }
boolean cursorPresent = c != null;
- mAutoRequery = autoRequery;
mCursor = c;
mDataValid = cursorPresent;
mContext = context;
mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
- mChangeObserver = new ChangeObserver();
+ if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
+ mChangeObserver = new ChangeObserver();
+ } else {
+ mChangeObserver = null;
+ }
+
if (cursorPresent) {
- c.registerContentObserver(mChangeObserver);
+ if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
c.registerDataSetObserver(mDataSetObserver);
}
}
@@ -246,13 +282,13 @@
return;
}
if (mCursor != null) {
- mCursor.unregisterContentObserver(mChangeObserver);
+ if (mChangeObserver != null) mCursor.unregisterContentObserver(mChangeObserver);
mCursor.unregisterDataSetObserver(mDataSetObserver);
mCursor.close();
}
mCursor = cursor;
if (cursor != null) {
- cursor.registerContentObserver(mChangeObserver);
+ if (mChangeObserver != null) cursor.registerContentObserver(mChangeObserver);
cursor.registerDataSetObserver(mDataSetObserver);
mRowIDColumn = cursor.getColumnIndexOrThrow("_id");
mDataValid = true;
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
index 7d3459e..d1c2270 100644
--- a/core/java/android/widget/SimpleCursorAdapter.java
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -62,7 +62,8 @@
private int mStringConversionColumn = -1;
private CursorToStringConverter mCursorToStringConverter;
private ViewBinder mViewBinder;
- private String[] mOriginalFrom;
+
+ String[] mOriginalFrom;
/**
* Constructor.
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
index fa47ff6..23e2277 100644
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -30,7 +30,7 @@
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import java.text.DateFormatSymbols;
@@ -39,7 +39,7 @@
/**
* Displays the time
*/
-public class DigitalClock extends LinearLayout {
+public class DigitalClock extends RelativeLayout {
private final static String M12 = "h:mm";
private final static String M24 = "kk:mm";
diff --git a/core/res/res/drawable/ic_btn_back.png b/core/res/res/drawable/ic_btn_back.png
new file mode 100644
index 0000000..c9bff4c
--- /dev/null
+++ b/core/res/res/drawable/ic_btn_back.png
Binary files differ
diff --git a/core/res/res/drawable/ic_btn_next.png b/core/res/res/drawable/ic_btn_next.png
new file mode 100755
index 0000000..c6cf436
--- /dev/null
+++ b/core/res/res/drawable/ic_btn_next.png
Binary files differ
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 04bc693..945d283 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -41,20 +41,21 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="22sp"
/>
- <!-- emergency call button shown when sim is missing or PUKd -->
- <Button
- android:id="@+id/emergencyCallButton"
+ <!-- "emergency calls only" shown when sim is missing or PUKd -->
+ <TextView
+ android:id="@+id/emergencyCallText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
+ android:layout_below="@id/carrier"
android:layout_alignParentRight="true"
- android:layout_marginTop="10dip"
+ android:layout_marginTop="0dip"
android:layout_marginRight="8dip"
- android:drawableLeft="@drawable/ic_emergency"
- style="@style/Widget.Button.Transparent"
- android:drawablePadding="8dip"
+ android:text="@string/emergency_calls_only"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/white"
/>
<!-- time and date -->
@@ -64,6 +65,7 @@
android:layout_below="@id/carrier"
android:layout_marginTop="52dip"
android:layout_marginLeft="20dip"
+ android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
@@ -71,7 +73,6 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:gravity="bottom"
android:textSize="72sp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
@@ -84,8 +85,9 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="bottom"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
android:ellipsize="none"
android:textSize="22sp"
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index 4774dfc..6b76004e 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -46,18 +46,19 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="22sp"
/>
- <!-- emergency call button shown when sim is missing or PUKd -->
- <Button
- android:id="@+id/emergencyCallButton"
+ <!-- "emergency calls only" shown when sim is missing or PUKd -->
+ <TextView
+ android:id="@+id/emergencyCallText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="20dip"
- android:drawableLeft="@drawable/ic_emergency"
- style="@style/Widget.Button.Transparent"
- android:drawablePadding="8dip"
+ android:text="@string/emergency_calls_only"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/white"
/>
<com.android.internal.widget.DigitalClock android:id="@+id/time"
@@ -65,12 +66,12 @@
android:layout_height="wrap_content"
android:layout_below="@id/carrier"
android:layout_marginTop="56dip"
+ android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="72sp"
@@ -85,8 +86,9 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="bottom"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
android:ellipsize="none"
android:textSize="22sp"
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index c1b406f..83381a1 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -58,18 +58,19 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
/>
+
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="72sp"
@@ -84,8 +85,9 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="bottom"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
android:ellipsize="none"
android:textSize="22sp"
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index 74a0eee..8dacfaf 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -55,12 +55,12 @@
android:layout_alignParentTop="true"
android:layout_marginTop="15dip"
android:layout_marginLeft="20dip"
+ android:layout_marginBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
android:singleLine="true"
android:ellipsize="none"
android:textSize="56sp"
@@ -74,8 +74,9 @@
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="bottom"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
android:ellipsize="none"
android:textSize="18sp"
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index 8f86981..844d338 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -4,22 +4,58 @@
**
** 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
+** 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
+** 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
+** 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.
*/
-->
-<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
- android:layout_width="match_parent"
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
android:layout_height="match_parent"
- android:drawSelectorOnTop="false"
- android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:layout_width="match_parent">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
/>
+
+ <RelativeLayout android:id="@+id/button_bar"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_weight="0"
+ android:background="@android:drawable/bottom_bar"
+ android:visibility="gone">
+
+ <Button android:id="@+id/back_button"
+ android:layout_width="150dip"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dip"
+ android:layout_alignParentLeft="true"
+ android:drawableLeft="@drawable/ic_btn_back"
+ android:drawablePadding="3dip"
+ android:text="@string/back_button_label"
+ />
+
+ <Button android:id="@+id/next_button"
+ android:layout_width="150dip"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dip"
+ android:layout_alignParentRight="true"
+ android:drawableRight="@drawable/ic_btn_next"
+ android:drawablePadding="3dip"
+ android:text="@string/next_button_label"
+ />
+ </RelativeLayout>
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a3ccaf7..d67f995 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1518,6 +1518,8 @@
will use only the number of items in the adapter and the number of items visible
on screen to determine the scrollbar's properties. -->
<attr name="smoothScrollbar" format="boolean" />
+ <!-- A reference to an XML description of the adapter to attach to the list. -->
+ <attr name="adapter" format="reference" />
</declare-styleable>
<declare-styleable name="AbsSpinner">
<!-- Reference to an array resource that will populate the Spinner. For static content,
@@ -2504,6 +2506,10 @@
<attr name="drawable" />
</declare-styleable>
+ <declare-styleable name="MipmapDrawableItem">
+ <attr name="drawable" />
+ </declare-styleable>
+
<declare-styleable name="RotateDrawable">
<attr name="visible" />
<attr name="fromDegrees" format="float" />
@@ -3644,5 +3650,68 @@
<declare-styleable name="RecognitionService">
<attr name="settingsActivity" />
</declare-styleable>
+
+ <!-- =============================== -->
+ <!-- Adapters attributes -->
+ <!-- =============================== -->
+ <eat-comment />
+
+ <!-- Adapter used to bind cursors. -->
+ <declare-styleable name="CursorAdapter">
+ <!-- URI to get the cursor from. Optional. -->
+ <attr name="uri" format="string" />
+ <!-- Selection statement for the query. Optional. -->
+ <attr name="selection" format="string" />
+ <!-- Sort order statement for the query. Optional. -->
+ <attr name="sortOrder" format="string" />
+ <!-- Layout resource used to display each row from the cursor. Mandatory. -->
+ <attr name="layout" />
+ </declare-styleable>
+
+ <!-- Attributes used in bind items for XML cursor adapters. -->
+ <declare-styleable name="CursorAdapter_BindItem">
+ <!-- The name of the column to bind from. Mandatory. -->
+ <attr name="from" format="string" />
+ <!-- The resource id of the view to bind to. Mandatory. -->
+ <attr name="to" format="reference" />
+ <!-- The type of binding. If this value is not specified, the type will be
+ inferred from the type of the "to" target view. Mandatory.
+
+ The type can be one of:
+ <ul>
+ <li>string, The content of the column is interpreted as a string.</li>
+ <li>image, The content of the column is interpreted as a blob describing an image.</li>
+ <li>image-uri, The content of the column is interpreted as a URI to an image.</li>
+ <li>drawable, The content of the column is interpreted as a resource id to a drawable.</li>
+ <li>A fully qualified class name, corresponding to an implementation of
+ android.widget.Adapters.CursorBinder.</li>
+ </ul>
+ -->
+ <attr name="as" format="string" />
+ </declare-styleable>
+
+ <!-- Attributes used in select items for XML cursor adapters. -->
+ <declare-styleable name="CursorAdapter_SelectItem">
+ <!-- The name of the column to select. Mandatory. -->
+ <attr name="column" format="string" />
+ </declare-styleable>
+
+ <!-- Attributes used to map values to new values in XML cursor adapters' bind items. -->
+ <declare-styleable name="CursorAdapter_MapItem">
+ <!-- The original value from the column. Mandatory. -->
+ <attr name="fromValue" format="string" />
+ <!-- The new value from the column. Mandatory. -->
+ <attr name="toValue" format="string" />
+ </declare-styleable>
+
+ <!-- Attributes used to map values to new values in XML cursor adapters' bind items. -->
+ <declare-styleable name="CursorAdapter_TransformItem">
+ <!-- The transformation expression. Mandatory if "withClass" is not specified. -->
+ <attr name="withExpression" format="string" />
+ <!-- The transformation class, an implementation of
+ android.widget.Adapters.CursorTransformation. Mandatory if "withExpression"
+ is not specified. -->
+ <attr name="withClass" format="string" />
+ </declare-styleable>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 98c3a0a..5678b1b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1238,5 +1238,22 @@
<public type="id" name="custom" id="0x0102002b" />
<public type="anim" name="cycle_interpolator" id="0x010a000c" />
+
+<!-- ===============================================================
+ Resources proposed for Gingerbread.
+ =============================================================== -->
+ <eat-comment />
+ <public type="attr" name="adapter" id="0x010102be" />
+ <public type="attr" name="selection" id="0x010102bf" />
+ <public type="attr" name="sortOrder" id="0x010102c0" />
+ <public type="attr" name="uri" id="0x010102c1" />
+ <public type="attr" name="from" id="0x010102c2" />
+ <public type="attr" name="to" id="0x010102c3" />
+ <public type="attr" name="as" id="0x010102c4" />
+ <public type="attr" name="fromValue" id="0x010102c5" />
+ <public type="attr" name="toValue" id="0x010102c6" />
+ <public type="attr" name="column" id="0x010102c7" />
+ <public type="attr" name="withExpression" id="0x010102c8" />
+ <public type="attr" name="withClass" id="0x010102c9" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 45d7aea..ee4d59d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2258,6 +2258,10 @@
<string name="tethered_notification_title">Tethering active</string>
<string name="tethered_notification_message">Touch to configure</string>
+ <!-- Strings for possible PreferenceActivity Back/Next buttons -->
+ <string name="back_button_label">Back</string>
+ <string name="next_button_label">Next</string>
+
<!-- Strings for throttling notification -->
<!-- Shown when the user is in danger of being throttled -->
<string name="throttle_warning_notification_title">High mobile data use</string>
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
index b9e9875..5262f29 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
@@ -56,7 +56,7 @@
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
- vCardParser = new VCardParser_V30(true); // use StrictParsing
+ vCardParser = new VCardParser_V30();
} else {
vCardParser = new VCardParser_V21();
}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
index 2edbb36..d50c95a 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
+++ b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
@@ -57,7 +57,7 @@
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
- vCardParser = new VCardParser_V30(true); // use StrictParsing
+ vCardParser = new VCardParser_V30();
} else {
vCardParser = new VCardParser_V21();
}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
index cfdd074..4cb3c1f 100644
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
@@ -63,7 +63,7 @@
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
- vCardParser = new VCardParser_V30(true); // Use StrictParsing.
+ vCardParser = new VCardParser_V30();
} else {
vCardParser = new VCardParser_V21();
}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
index 004a197..2de0464 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
@@ -275,6 +275,16 @@
testPhoneBasicCommon(V30);
}
+ public void testPhoneRefrainFormatting() {
+ mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING);
+ mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1234567890(abcdefghijklmnopqrstuvwxyz)")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "1234567890(abcdefghijklmnopqrstuvwxyz)",
+ new TypeSet("HOME"));
+ }
+
/**
* Tests that vCard composer emits corresponding type param which we expect.
*/
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
index 5b60342..c9e85dc 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
@@ -17,15 +17,13 @@
package android.pim.vcard;
import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
import java.util.Arrays;
public class VCardJapanizationTests extends VCardTestsBase {
@@ -114,7 +112,7 @@
.put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
final ContentValues contentValues =
- (VCardConfig.usesShiftJis(vcardType) ?
+ (VCardConfig.shouldUseShiftJisForExport(vcardType) ?
(VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
mContentValuesForQPAndSJis) :
(VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
@@ -214,7 +212,7 @@
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
- ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ?
+ ContentValues contentValues = (VCardConfig.shouldUseShiftJisForExport(vcardType) ?
(VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
mContentValuesForQPAndSJis) :
(VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
index bfc3158..4377f0c 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
@@ -209,12 +209,11 @@
}
}
- final VCardParser parser =
- (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
+ final VCardParser parser = (mIsV30 ? new VCardParser_V30() : new VCardParser_V21());
InputStream is = null;
try {
String charset =
- (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8");
+ (VCardConfig.shouldUseShiftJisForExport(mVCardType) ? "SHIFT_JIS" : "UTF-8");
is = new ByteArrayInputStream(vcard.getBytes(charset));
mTestCase.assertEquals(true, parser.parse(is, null, builder));
} catch (IOException e) {
@@ -292,8 +291,7 @@
while (!composer.isAfterLast()) {
try {
final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
- mTestCase.assertTrue(
- composer.createOneEntry(getMockGetEntityIteratorMethod()));
+ mTestCase.assertTrue(composer.createOneEntry(getMockGetEntityIteratorMethod()));
} catch (Exception e) {
e.printStackTrace();
mTestCase.fail();
diff --git a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
index 0e6c325..1e607dd 100644
--- a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
+++ b/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
@@ -68,7 +68,7 @@
private boolean mStrictLineBreakParsing;
public VNodeBuilder() {
- this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false);
+ this(VCardConfig.DEFAULT_IMPORT_CHARSET, TARGET_CHARSET, false);
}
public VNodeBuilder(String charset, boolean strictLineBreakParsing) {
@@ -83,7 +83,7 @@
if (sourceCharset != null) {
mSourceCharset = sourceCharset;
} else {
- mSourceCharset = VCardConfig.DEFAULT_CHARSET;
+ mSourceCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
if (targetCharset != null) {
mTargetCharset = targetCharset;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index 8e7e63e..fb0f0c1 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -22,7 +22,7 @@
import junit.framework.TestCase;
/**
- * Tests StaticLayout bidi implementation.
+ * Quick check of native bidi implementation.
*/
public class StaticLayoutBidiTest extends TestCase {
@@ -41,73 +41,47 @@
//@SmallTest
public void testAllLtr() {
- expectBidi(REQ_DL, "a test", "000000", L);
+ expectNativeBidi(REQ_DL, "a test", "000000", L);
}
//@SmallTest
public void testLtrRtl() {
- expectBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
+ expectNativeBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
}
//@SmallTest
public void testAllRtl() {
- expectBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
+ expectNativeBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
}
//@SmallTest
public void testRtlLtr() {
- expectBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111000", R);
+ expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
}
//@SmallTest
public void testRAllLtr() {
- expectBidi(REQ_R, "a test", "000000", R);
+ expectNativeBidi(REQ_R, "a test", "222222", R);
}
//@SmallTest
public void testRLtrRtl() {
- expectBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "0001111", R);
+ expectNativeBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "2221111", R);
}
//@SmallTest
public void testLAllRtl() {
- expectBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
+ expectNativeBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
}
//@SmallTest
public void testLRtlLtr() {
- expectBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L);
- }
-
- private void expectBidi(int dir, String text,
- String expectedLevels, int expectedDir) {
- char[] chs = text.toCharArray();
- int n = chs.length;
- byte[] chInfo = new byte[n];
-
- int resultDir = StaticLayout.bidi(dir, chs, chInfo, n, false);
-
- {
- StringBuilder sb = new StringBuilder("info:");
- for (int i = 0; i < n; ++i) {
- sb.append(" ").append(String.valueOf(chInfo[i]));
- }
- Log.i("BIDI", sb.toString());
- }
-
- char[] resultLevelChars = new char[n];
- for (int i = 0; i < n; ++i) {
- resultLevelChars[i] = (char)('0' + chInfo[i]);
- }
- String resultLevels = new String(resultLevelChars);
- assertEquals("direction", expectedDir, resultDir);
- assertEquals("levels", expectedLevels, resultLevels);
+ expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
}
//@SmallTest
public void testNativeBidi() {
- // native bidi returns levels, not simply directions
- expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
+ expectNativeBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L);
}
private void expectNativeBidi(int dir, String text,
diff --git a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
new file mode 100644
index 0000000..4fde849
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2010 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 android.text;
+
+import android.text.Layout.Directions;
+import android.text.StaticLayoutTest.LayoutBuilder;
+
+import java.util.Arrays;
+import java.util.Formatter;
+
+import junit.framework.TestCase;
+
+public class StaticLayoutDirectionsTest extends TestCase {
+ private static final char ALEF = '\u05d0';
+
+ private static Directions dirs(int ... dirs) {
+ return new Directions(dirs);
+ }
+
+ // constants from Layout that are package-protected
+ private static final int RUN_LENGTH_MASK = 0x03ffffff;
+ private static final int RUN_LEVEL_SHIFT = 26;
+ private static final int RUN_LEVEL_MASK = 0x3f;
+ private static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
+
+ private static final Directions DIRS_ALL_LEFT_TO_RIGHT =
+ new Directions(new int[] { 0, RUN_LENGTH_MASK });
+ private static final Directions DIRS_ALL_RIGHT_TO_LEFT =
+ new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
+
+ private static final int LVL1_1 = 1 | (1 << RUN_LEVEL_SHIFT);
+ private static final int LVL2_1 = 1 | (2 << RUN_LEVEL_SHIFT);
+ private static final int LVL2_2 = 2 | (2 << RUN_LEVEL_SHIFT);
+
+ private static String[] texts = {
+ "",
+ " ",
+ "a",
+ "a1",
+ "aA",
+ "a1b",
+ "a1A",
+ "aA1",
+ "aAb",
+ "aA1B",
+ "aA1B2",
+
+ // rtl
+ "A",
+ "A1",
+ "Aa",
+ "A1B",
+ "A1a",
+ "Aa1",
+ "AaB"
+ };
+
+ // Expected directions are an array of start/length+level pairs,
+ // in visual order from the leading margin.
+ private static Directions[] expected = {
+ DIRS_ALL_LEFT_TO_RIGHT,
+ DIRS_ALL_LEFT_TO_RIGHT,
+ DIRS_ALL_LEFT_TO_RIGHT,
+ DIRS_ALL_LEFT_TO_RIGHT,
+ dirs(0, 1, 1, LVL1_1),
+ DIRS_ALL_LEFT_TO_RIGHT,
+ dirs(0, 2, 2, LVL1_1),
+ dirs(0, 1, 2, LVL2_1, 1, LVL1_1),
+ dirs(0, 1, 1, LVL1_1, 2, 1),
+ dirs(0, 1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
+ dirs(0, 1, 4, LVL2_1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
+
+ // rtl
+ DIRS_ALL_RIGHT_TO_LEFT,
+ dirs(0, LVL1_1, 1, LVL2_1),
+ dirs(0, LVL1_1, 1, LVL2_1),
+ dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
+ dirs(0, LVL1_1, 1, LVL2_2),
+ dirs(0, LVL1_1, 1, LVL2_2),
+ dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
+ };
+
+ private static String pseudoBidiToReal(String src) {
+ char[] chars = src.toCharArray();
+ for (int j = 0; j < chars.length; ++j) {
+ char c = chars[j];
+ if (c >= 'A' && c <= 'D') {
+ chars[j] = (char)(ALEF + c - 'A');
+ }
+ }
+
+ return new String(chars, 0, chars.length);
+ }
+
+ // @SmallTest
+ public void testDirections() {
+ StringBuilder buf = new StringBuilder("\n");
+ Formatter f = new Formatter(buf);
+
+ LayoutBuilder b = StaticLayoutTest.builder();
+ for (int i = 0; i < texts.length; ++i) {
+ b.setText(pseudoBidiToReal(texts[i]));
+ checkDirections(b.build(), i, b.text, expected, f);
+ }
+ if (buf.length() > 1) {
+ fail(buf.toString());
+ }
+ }
+
+ // @SmallTest
+ public void testTrailingWhitespace() {
+ LayoutBuilder b = StaticLayoutTest.builder();
+ b.setText(pseudoBidiToReal("Ab c"));
+ float width = b.paint.measureText(b.text, 0, 5); // exclude 'c'
+ b.setWidth(Math.round(width));
+ Layout l = b.build();
+ if (l.getLineCount() != 2) {
+ throw new RuntimeException("expected 2 lines, got: " + l.getLineCount());
+ }
+ Directions result = l.getLineDirections(0);
+ Directions expected = dirs(0, LVL1_1, 1, LVL2_1, 2, 3 | (1 << Layout.RUN_LEVEL_SHIFT));
+ expectDirections("split line", expected, result);
+ }
+
+ public void testNextToRightOf() {
+ LayoutBuilder b = StaticLayoutTest.builder();
+ b.setText(pseudoBidiToReal("aA1B2"));
+ // visual a2B1A positions 04321
+ // 0: |a2B1A, strong is sol, after -> 0
+ // 1: a|2B1A, strong is a, after ->, 1
+ // 2: a2|B1A, strong is B, after -> 4
+ // 3: a2B|1A, strong is B, before -> 3
+ // 4: a2B1|A, strong is A, after -> 2
+ // 5: a2B1A|, strong is eol, before -> 5
+ int[] expected = { 0, 1, 4, 3, 2, 5 };
+ Layout l = b.build();
+ int n = 0;
+ for (int i = 1; i < expected.length; ++i) {
+ int t = l.getOffsetToRightOf(n);
+ if (t != expected[i]) {
+ fail("offset[" + i + "] to right of: " + n + " expected: " +
+ expected[i] + " got: " + t);
+ }
+ n = t;
+ }
+ }
+
+ public void testNextToLeftOf() {
+ LayoutBuilder b = StaticLayoutTest.builder();
+ b.setText(pseudoBidiToReal("aA1B2"));
+ int[] expected = { 0, 1, 4, 3, 2, 5 };
+ Layout l = b.build();
+ int n = 5;
+ for (int i = expected.length - 1; --i >= 0;) {
+ int t = l.getOffsetToLeftOf(n);
+ if (t != expected[i]) {
+ fail("offset[" + i + "] to left of: " + n + " expected: " +
+ expected[i] + " got: " + t);
+ }
+ n = t;
+ }
+ }
+
+ // utility, not really a test
+ /*
+ public void testMeasureText1() {
+ LayoutBuilder b = StaticLayoutTest.builder();
+ String text = "ABC"; // "abAB"
+ b.setText(pseudoBidiToReal(text));
+ Layout l = b.build();
+ Directions directions = l.getLineDirections(0);
+
+ TextPaint workPaint = new TextPaint();
+
+ int dir = -1; // LEFT_TO_RIGHT
+ boolean trailing = true;
+ boolean alt = true;
+ do {
+ dir = -dir;
+ do {
+ trailing = !trailing;
+ for (int offset = 0, end = b.text.length(); offset <= end; ++offset) {
+ float width = Layout.measureText(b.paint,
+ workPaint,
+ b.text,
+ 0, offset, end,
+ dir, directions,
+ trailing, false,
+ null);
+ Log.i("BIDI", "dir: " + dir + " trail: " + trailing +
+ " offset: " + offset + " width: " + width);
+ }
+ } while (!trailing);
+ } while (dir > 0);
+ }
+ */
+
+ // utility for displaying arrays in hex
+ private static String hexArray(int[] array) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ for (int i : array) {
+ if (sb.length() > 1) {
+ sb.append(", ");
+ }
+ sb.append(Integer.toHexString(i));
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ private void checkDirections(Layout l, int i, String text,
+ Directions[] expectedDirs, Formatter f) {
+ Directions expected = expectedDirs[i];
+ Directions result = l.getLineDirections(0);
+ if (!Arrays.equals(expected.mDirections, result.mDirections)) {
+ f.format("%n[%2d] '%s', %s != %s", i, text,
+ hexArray(expected.mDirections),
+ hexArray(result.mDirections));
+ }
+ }
+
+ private void expectDirections(String msg, Directions expected, Directions result) {
+ if (!Arrays.equals(expected.mDirections, result.mDirections)) {
+ fail("expected: " + hexArray(expected.mDirections) +
+ " got: " + hexArray(result.mDirections));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 1f58a2c..d554a50 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -228,11 +228,11 @@
}
}
- private static LayoutBuilder builder() {
+ /* package */ static LayoutBuilder builder() {
return new LayoutBuilder();
}
- private static class LayoutBuilder {
+ /* package */ static class LayoutBuilder {
String text = "This is a test";
TextPaint paint = new TextPaint(); // default
int width = 100;
diff --git a/core/tests/coretests/src/android/util/ExpandableListScenario.java b/core/tests/coretests/src/android/util/ExpandableListScenario.java
deleted file mode 100644
index 4a12b0d..0000000
--- a/core/tests/coretests/src/android/util/ExpandableListScenario.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2007 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 android.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * Utility base class for creating various Expandable List scenarios.
- * <p>
- * WARNING: A lot of the features are mixed between ListView's expected position
- * (flat list position) and an ExpandableListView's expected position. You must add/change
- * features as you need them.
- *
- * @see ListScenario
- */
-public abstract class ExpandableListScenario extends ListScenario {
- protected ExpandableListAdapter mAdapter;
- protected List<MyGroup> mGroups;
-
- @Override
- protected ListView createListView() {
- return new ExpandableListView(this);
- }
-
- @Override
- protected Params createParams() {
- return new ExpandableParams();
- }
-
- @Override
- protected void setAdapter(ListView listView) {
- ((ExpandableListView) listView).setAdapter(mAdapter = createAdapter());
- }
-
- protected ExpandableListAdapter createAdapter() {
- return new MyAdapter();
- }
-
- @Override
- protected void readAndValidateParams(Params params) {
- ExpandableParams expandableParams = (ExpandableParams) params;
-
- int[] numChildren = expandableParams.mNumChildren;
-
- mGroups = new ArrayList<MyGroup>(numChildren.length);
- for (int i = 0; i < numChildren.length; i++) {
- mGroups.add(new MyGroup(numChildren[i]));
- }
-
- expandableParams.superSetNumItems();
-
- super.readAndValidateParams(params);
- }
-
- /**
- * Get the ExpandableListView widget.
- * @return The main widget.
- */
- public ExpandableListView getExpandableListView() {
- return (ExpandableListView) super.getListView();
- }
-
- public static class ExpandableParams extends Params {
- private int[] mNumChildren;
-
- /**
- * Sets the number of children per group.
- *
- * @param numChildrenPerGroup The number of children per group.
- */
- public ExpandableParams setNumChildren(int[] numChildren) {
- mNumChildren = numChildren;
- return this;
- }
-
- /**
- * Sets the number of items on the superclass based on the number of
- * groups and children per group.
- */
- private ExpandableParams superSetNumItems() {
- int numItems = 0;
-
- if (mNumChildren != null) {
- for (int i = mNumChildren.length - 1; i >= 0; i--) {
- numItems += mNumChildren[i];
- }
- }
-
- super.setNumItems(numItems);
-
- return this;
- }
-
- @Override
- public Params setNumItems(int numItems) {
- throw new IllegalStateException("Use setNumGroups and setNumChildren instead.");
- }
-
- @Override
- public ExpandableParams setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor) {
- return (ExpandableParams) super.setFadingEdgeScreenSizeFactor(fadingEdgeScreenSizeFactor);
- }
-
- @Override
- public ExpandableParams setItemScreenSizeFactor(double itemScreenSizeFactor) {
- return (ExpandableParams) super.setItemScreenSizeFactor(itemScreenSizeFactor);
- }
-
- @Override
- public ExpandableParams setItemsFocusable(boolean itemsFocusable) {
- return (ExpandableParams) super.setItemsFocusable(itemsFocusable);
- }
-
- @Override
- public ExpandableParams setMustFillScreen(boolean fillScreen) {
- return (ExpandableParams) super.setMustFillScreen(fillScreen);
- }
-
- @Override
- public ExpandableParams setPositionScreenSizeFactorOverride(int position, double itemScreenSizeFactor) {
- return (ExpandableParams) super.setPositionScreenSizeFactorOverride(position, itemScreenSizeFactor);
- }
-
- @Override
- public ExpandableParams setPositionUnselectable(int position) {
- return (ExpandableParams) super.setPositionUnselectable(position);
- }
-
- @Override
- public ExpandableParams setStackFromBottom(boolean stackFromBottom) {
- return (ExpandableParams) super.setStackFromBottom(stackFromBottom);
- }
-
- @Override
- public ExpandableParams setStartingSelectionPosition(int startingSelectionPosition) {
- return (ExpandableParams) super.setStartingSelectionPosition(startingSelectionPosition);
- }
-
- @Override
- public ExpandableParams setConnectAdapter(boolean connectAdapter) {
- return (ExpandableParams) super.setConnectAdapter(connectAdapter);
- }
- }
-
- /**
- * Gets a string for the value of some item.
- * @param packedPosition The position of the item.
- * @return The string.
- */
- public final String getValueAtPosition(long packedPosition) {
- final int type = ExpandableListView.getPackedPositionType(packedPosition);
-
- if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
- return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
- .children.get(ExpandableListView.getPackedPositionChild(packedPosition))
- .name;
- } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
- return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
- .name;
- } else {
- throw new IllegalStateException("packedPosition is not a valid position.");
- }
- }
-
- /**
- * Whether a particular position is out of bounds.
- *
- * @param packedPosition The packed position.
- * @return Whether it's out of bounds.
- */
- private boolean isOutOfBounds(long packedPosition) {
- final int type = ExpandableListView.getPackedPositionType(packedPosition);
-
- if (type == ExpandableListView.PACKED_POSITION_TYPE_NULL) {
- throw new IllegalStateException("packedPosition is not a valid position.");
- }
-
- final int group = ExpandableListView.getPackedPositionGroup(packedPosition);
- if (group >= mGroups.size() || group < 0) {
- return true;
- }
-
- if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
- final int child = ExpandableListView.getPackedPositionChild(packedPosition);
- if (child >= mGroups.get(group).children.size() || child < 0) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Gets a view for the packed position, possibly reusing the convertView.
- *
- * @param packedPosition The position to get a view for.
- * @param convertView Optional view to convert.
- * @param parent The future parent.
- * @return A view.
- */
- private View getView(long packedPosition, View convertView, ViewGroup parent) {
- if (isOutOfBounds(packedPosition)) {
- throw new IllegalStateException("position out of range for adapter!");
- }
-
- final ExpandableListView elv = getExpandableListView();
- final int flPos = elv.getFlatListPosition(packedPosition);
-
- if (convertView != null) {
- ((TextView) convertView).setText(getValueAtPosition(packedPosition));
- convertView.setId(flPos);
- return convertView;
- }
-
- int desiredHeight = getHeightForPosition(flPos);
- return createView(packedPosition, flPos, parent, desiredHeight);
- }
-
- /**
- * Create a view for a group or child position.
- *
- * @param packedPosition The packed position (has type, group pos, and optionally child pos).
- * @param flPos The flat list position (the position that the ListView goes by).
- * @param parent The parent view.
- * @param desiredHeight The desired height.
- * @return A view.
- */
- protected View createView(long packedPosition, int flPos, ViewGroup parent, int desiredHeight) {
- TextView result = new TextView(parent.getContext());
- result.setHeight(desiredHeight);
- result.setText(getValueAtPosition(packedPosition));
- final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- result.setLayoutParams(lp);
- result.setGravity(Gravity.CENTER_VERTICAL);
- result.setPadding(36, 0, 0, 0);
- result.setId(flPos);
- return result;
- }
-
- /**
- * Returns a group index containing either the number of children or at
- * least one child.
- *
- * @param numChildren The group must have this amount, or -1 if using
- * atLeastOneChild.
- * @param atLeastOneChild The group must have at least one child, or false
- * if using numChildren.
- * @return A group index with the requirements.
- */
- public int findGroupWithNumChildren(int numChildren, boolean atLeastOneChild) {
- final ExpandableListAdapter adapter = mAdapter;
-
- for (int i = adapter.getGroupCount() - 1; i >= 0; i--) {
- final int curNumChildren = adapter.getChildrenCount(i);
-
- if (numChildren == curNumChildren || atLeastOneChild && curNumChildren > 0) {
- return i;
- }
- }
-
- return -1;
- }
-
- public List<MyGroup> getGroups() {
- return mGroups;
- }
-
- public ExpandableListAdapter getAdapter() {
- return mAdapter;
- }
-
- /**
- * Simple expandable list adapter.
- */
- protected class MyAdapter extends BaseExpandableListAdapter {
- public Object getChild(int groupPosition, int childPosition) {
- return getValueAtPosition(ExpandableListView.getPackedPositionForChild(groupPosition,
- childPosition));
- }
-
- public long getChildId(int groupPosition, int childPosition) {
- return mGroups.get(groupPosition).children.get(childPosition).id;
- }
-
- public int getChildrenCount(int groupPosition) {
- return mGroups.get(groupPosition).children.size();
- }
-
- public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
- View convertView, ViewGroup parent) {
- return getView(ExpandableListView.getPackedPositionForChild(groupPosition,
- childPosition), convertView, parent);
- }
-
- public Object getGroup(int groupPosition) {
- return getValueAtPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
- }
-
- public int getGroupCount() {
- return mGroups.size();
- }
-
- public long getGroupId(int groupPosition) {
- return mGroups.get(groupPosition).id;
- }
-
- public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
- ViewGroup parent) {
- return getView(ExpandableListView.getPackedPositionForGroup(groupPosition),
- convertView, parent);
- }
-
- public boolean isChildSelectable(int groupPosition, int childPosition) {
- return true;
- }
-
- public boolean hasStableIds() {
- return true;
- }
-
- }
-
- public static class MyGroup {
- private static long mNextId = 1000;
-
- String name;
- long id = mNextId++;
- List<MyChild> children;
-
- public MyGroup(int numChildren) {
- name = "Group " + id;
- children = new ArrayList<MyChild>(numChildren);
- for (int i = 0; i < numChildren; i++) {
- children.add(new MyChild());
- }
- }
- }
-
- public static class MyChild {
- private static long mNextId = 2000;
-
- String name;
- long id = mNextId++;
-
- public MyChild() {
- name = "Child " + id;
- }
- }
-
- @Override
- protected final void init(Params params) {
- init((ExpandableParams) params);
- }
-
- /**
- * @see ListScenario#init
- */
- protected abstract void init(ExpandableParams params);
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java
deleted file mode 100644
index e23b516..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2007 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 android.widget.expandablelistview;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.ExpandableListScenario;
-import android.util.ListUtil;
-import android.util.ExpandableListScenario.MyGroup;
-import android.view.KeyEvent;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-
-import java.util.List;
-
-public class ExpandableListBasicTest extends ActivityInstrumentationTestCase2<ExpandableListSimple> {
- private ExpandableListScenario mActivity;
- private ExpandableListView mExpandableListView;
- private ExpandableListAdapter mAdapter;
- private ListUtil mListUtil;
-
- public ExpandableListBasicTest() {
- super(ExpandableListSimple.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mActivity = getActivity();
- mExpandableListView = mActivity.getExpandableListView();
- mAdapter = mExpandableListView.getExpandableListAdapter();
- mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
- }
-
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mActivity);
- assertNotNull(mExpandableListView);
- }
-
- private int expandGroup(int numChildren, boolean atLeastOneChild) {
- final int groupPos = mActivity.findGroupWithNumChildren(numChildren, atLeastOneChild);
- assertTrue("Could not find group to expand", groupPos >= 0);
-
- assertFalse("Group is already expanded", mExpandableListView.isGroupExpanded(groupPos));
- mListUtil.arrowScrollToSelectedPosition(groupPos);
- getInstrumentation().waitForIdleSync();
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(groupPos));
-
- return groupPos;
- }
-
- @MediumTest
- public void testExpandGroup() {
- expandGroup(-1, true);
- }
-
- @MediumTest
- public void testCollapseGroup() {
- final int groupPos = expandGroup(-1, true);
-
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertFalse("Group did not collapse", mExpandableListView.isGroupExpanded(groupPos));
- }
-
- @MediumTest
- public void testExpandedGroupMovement() {
- // Expand the first group
- mListUtil.arrowScrollToSelectedPosition(0);
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
-
- // Ensure it expanded
- assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(0));
-
- // Wait until that's all good
- getInstrumentation().waitForIdleSync();
-
- // Make sure it expanded
- assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(0));
-
- // Insert a collapsed group in front of the one just expanded
- List<MyGroup> groups = mActivity.getGroups();
- MyGroup insertedGroup = new MyGroup(1);
- groups.add(0, insertedGroup);
-
- // Notify data change
- assertTrue("Adapter is not an instance of the base adapter",
- mAdapter instanceof BaseExpandableListAdapter);
- final BaseExpandableListAdapter adapter = (BaseExpandableListAdapter) mAdapter;
-
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- adapter.notifyDataSetChanged();
- }
- });
- getInstrumentation().waitForIdleSync();
-
- // Make sure the right group is expanded
- assertTrue("The expanded state didn't stay with the proper group",
- mExpandableListView.isGroupExpanded(1));
- assertFalse("The expanded state was given to the inserted group",
- mExpandableListView.isGroupExpanded(0));
- }
-
- @MediumTest
- public void testContextMenus() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testContextMenus();
- }
-
- @MediumTest
- public void testConvertionBetweenFlatAndPacked() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testConvertionBetweenFlatAndPackedOnGroups();
- tester.testConvertionBetweenFlatAndPackedOnChildren();
- }
-
- @MediumTest
- public void testSelectedPosition() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testSelectedPositionOnGroups();
- tester.testSelectedPositionOnChildren();
- }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java
deleted file mode 100644
index 78db28c..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2007 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 android.widget.expandablelistview;
-
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.widget.BaseExpandableListAdapter;
-
-import android.util.ExpandableListScenario;
-
-public class ExpandableListSimple extends ExpandableListScenario {
- private static final int[] NUM_CHILDREN = {4, 3, 2, 1, 0};
-
- @Override
- protected void init(ExpandableParams params) {
- params.setNumChildren(NUM_CHILDREN)
- .setItemScreenSizeFactor(0.14);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
-
- menu.add("Add item").setOnMenuItemClickListener(new OnMenuItemClickListener() {
- public boolean onMenuItemClick(MenuItem item) {
- mGroups.add(0, new MyGroup(2));
- ((BaseExpandableListAdapter) mAdapter).notifyDataSetChanged();
- return true;
- }
- });
-
- return true;
- }
-
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java
deleted file mode 100644
index dfb10fb..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2007 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 android.widget.expandablelistview;
-
-import android.app.Instrumentation;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.ExpandableListScenario;
-import android.util.ListUtil;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-
-import junit.framework.Assert;
-
-public class ExpandableListTester {
- private final ExpandableListView mExpandableListView;
- private final ExpandableListAdapter mAdapter;
- private final ListUtil mListUtil;
-
- private final ActivityInstrumentationTestCase2<? extends ExpandableListScenario>
- mActivityInstrumentation;
-
- Instrumentation mInstrumentation;
-
- public ExpandableListTester(
- ExpandableListView expandableListView,
- ActivityInstrumentationTestCase2<? extends ExpandableListScenario>
- activityInstrumentation) {
- mExpandableListView = expandableListView;
- Instrumentation instrumentation = activityInstrumentation.getInstrumentation();
- mListUtil = new ListUtil(mExpandableListView, instrumentation);
- mAdapter = mExpandableListView.getExpandableListAdapter();
- mActivityInstrumentation = activityInstrumentation;
- mInstrumentation = mActivityInstrumentation.getInstrumentation();
- }
-
- private void expandGroup(final int groupIndex, int flatPosition) {
- Assert.assertFalse("Group is already expanded", mExpandableListView
- .isGroupExpanded(groupIndex));
- mListUtil.arrowScrollToSelectedPosition(flatPosition);
- mInstrumentation.waitForIdleSync();
- mActivityInstrumentation.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- mActivityInstrumentation.getInstrumentation().waitForIdleSync();
- Assert.assertTrue("Group did not expand " + groupIndex,
- mExpandableListView.isGroupExpanded(groupIndex));
- }
-
- void testContextMenus() {
- // Add a position tester ContextMenu listener to the ExpandableListView
- PositionTesterContextMenuListener menuListener = new PositionTesterContextMenuListener();
- mExpandableListView.setOnCreateContextMenuListener(menuListener);
-
- int index = 0;
-
- // Scrolling on header elements should trigger an AdapterContextMenu
- for (int i=0; i<mExpandableListView.getHeaderViewsCount(); i++) {
- // Check group index in context menu
- menuListener.expectAdapterContextMenu(i);
- // Make sure the group is visible so that getChild finds it
- mListUtil.arrowScrollToSelectedPosition(index);
- View headerChild = mExpandableListView.getChildAt(index
- - mExpandableListView.getFirstVisiblePosition());
- mExpandableListView.showContextMenuForChild(headerChild);
- mInstrumentation.waitForIdleSync();
- Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
- index++;
- }
-
- int groupCount = mAdapter.getGroupCount();
- for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
-
- // Expand group
- expandGroup(groupIndex, index);
-
- // Check group index in context menu
- menuListener.expectGroupContextMenu(groupIndex);
- // Make sure the group is visible so that getChild finds it
- mListUtil.arrowScrollToSelectedPosition(index);
- View groupChild = mExpandableListView.getChildAt(index
- - mExpandableListView.getFirstVisiblePosition());
- mExpandableListView.showContextMenuForChild(groupChild);
- mInstrumentation.waitForIdleSync();
- Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
- index++;
-
- final int childrenCount = mAdapter.getChildrenCount(groupIndex);
- for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
- // Check child index in context menu
- mListUtil.arrowScrollToSelectedPosition(index);
- menuListener.expectChildContextMenu(groupIndex, childIndex);
- View child = mExpandableListView.getChildAt(index
- - mExpandableListView.getFirstVisiblePosition());
- mExpandableListView.showContextMenuForChild(child);
- mInstrumentation.waitForIdleSync();
- Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
- index++;
- }
- }
-
- // Scrolling on footer elements should trigger an AdapterContextMenu
- for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
- // Check group index in context menu
- menuListener.expectAdapterContextMenu(index);
- // Make sure the group is visible so that getChild finds it
- mListUtil.arrowScrollToSelectedPosition(index);
- View footerChild = mExpandableListView.getChildAt(index
- - mExpandableListView.getFirstVisiblePosition());
- mExpandableListView.showContextMenuForChild(footerChild);
- mInstrumentation.waitForIdleSync();
- Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
- index++;
- }
-
- // Cleanup: remove the listener we added.
- mExpandableListView.setOnCreateContextMenuListener(null);
- }
-
- private int expandAGroup() {
- final int groupIndex = 2;
- final int headerCount = mExpandableListView.getHeaderViewsCount();
- Assert.assertTrue("Not enough groups", groupIndex < mAdapter.getGroupCount());
- expandGroup(groupIndex, groupIndex + headerCount);
- return groupIndex;
- }
-
- // This method assumes that NO group is expanded when called
- void testConvertionBetweenFlatAndPackedOnGroups() {
- final int headerCount = mExpandableListView.getHeaderViewsCount();
-
- for (int i=0; i<headerCount; i++) {
- Assert.assertEquals("Non NULL position for header item",
- ExpandableListView.PACKED_POSITION_VALUE_NULL,
- mExpandableListView.getExpandableListPosition(i));
- }
-
- // Test all (non expanded) groups
- final int groupCount = mAdapter.getGroupCount();
- for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
- int expectedFlatPosition = headerCount + groupIndex;
- long packedPositionForGroup = ExpandableListView.getPackedPositionForGroup(groupIndex);
- Assert.assertEquals("Group not found at flat position " + expectedFlatPosition,
- packedPositionForGroup,
- mExpandableListView.getExpandableListPosition(expectedFlatPosition));
-
- Assert.assertEquals("Wrong flat position for group " + groupIndex,
- expectedFlatPosition,
- mExpandableListView.getFlatListPosition(packedPositionForGroup));
- }
-
- for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
- Assert.assertEquals("Non NULL position for header item",
- ExpandableListView.PACKED_POSITION_VALUE_NULL,
- mExpandableListView.getExpandableListPosition(headerCount + groupCount + i));
- }
- }
-
- // This method assumes that NO group is expanded when called
- void testConvertionBetweenFlatAndPackedOnChildren() {
- // Test with an expanded group
- final int headerCount = mExpandableListView.getHeaderViewsCount();
- final int groupIndex = expandAGroup();
-
- final int childrenCount = mAdapter.getChildrenCount(groupIndex);
- for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
- int expectedFlatPosition = headerCount + groupIndex + 1 + childIndex;
- long childPos = ExpandableListView.getPackedPositionForChild(groupIndex, childIndex);
-
- Assert.assertEquals("Wrong flat position for child ",
- childPos,
- mExpandableListView.getExpandableListPosition(expectedFlatPosition));
-
- Assert.assertEquals("Wrong flat position for child ",
- expectedFlatPosition,
- mExpandableListView.getFlatListPosition(childPos));
- }
- }
-
- // This method assumes that NO group is expanded when called
- void testSelectedPositionOnGroups() {
- int index = 0;
-
- // Scrolling on header elements should not give a valid selected position.
- for (int i=0; i<mExpandableListView.getHeaderViewsCount(); i++) {
- mListUtil.arrowScrollToSelectedPosition(index);
- Assert.assertEquals("Header item is selected",
- ExpandableListView.PACKED_POSITION_VALUE_NULL,
- mExpandableListView.getSelectedPosition());
- index++;
- }
-
- // Check selection on group items
- final int groupCount = mAdapter.getGroupCount();
- for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
- mListUtil.arrowScrollToSelectedPosition(index);
- Assert.assertEquals("Group item is not selected",
- ExpandableListView.getPackedPositionForGroup(groupIndex),
- mExpandableListView.getSelectedPosition());
- index++;
- }
-
- // Scrolling on footer elements should not give a valid selected position.
- for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
- mListUtil.arrowScrollToSelectedPosition(index);
- Assert.assertEquals("Footer item is selected",
- ExpandableListView.PACKED_POSITION_VALUE_NULL,
- mExpandableListView.getSelectedPosition());
- index++;
- }
- }
-
- // This method assumes that NO group is expanded when called
- void testSelectedPositionOnChildren() {
- // Test with an expanded group
- final int headerCount = mExpandableListView.getHeaderViewsCount();
- final int groupIndex = expandAGroup();
-
- final int childrenCount = mAdapter.getChildrenCount(groupIndex);
- for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
- int childFlatPosition = headerCount + groupIndex + 1 + childIndex;
- mListUtil.arrowScrollToSelectedPosition(childFlatPosition);
- Assert.assertEquals("Group item is not selected",
- ExpandableListView.getPackedPositionForChild(groupIndex, childIndex),
- mExpandableListView.getSelectedPosition());
- }
- }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java
deleted file mode 100644
index 2251c1d..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2007 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 android.widget.expandablelistview;
-
-import android.os.Bundle;
-import android.util.ExpandableListScenario;
-import android.widget.Button;
-import android.widget.ExpandableListView;
-
-public class ExpandableListWithHeaders extends ExpandableListScenario {
- private static final int[] sNumChildren = {1, 4, 3, 2, 6};
- private static final int sNumOfHeadersAndFooters = 12;
-
- @Override
- protected void init(ExpandableParams params) {
- params.setStackFromBottom(false)
- .setStartingSelectionPosition(-1)
- .setNumChildren(sNumChildren)
- .setItemScreenSizeFactor(0.14)
- .setConnectAdapter(false);
- }
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final ExpandableListView expandableListView = getExpandableListView();
- expandableListView.setItemsCanFocus(true);
-
- for (int i = 0; i < sNumOfHeadersAndFooters; i++) {
- Button header = new Button(this);
- header.setText("Header View " + i);
- expandableListView.addHeaderView(header);
- }
-
- for (int i = 0; i < sNumOfHeadersAndFooters; i++) {
- Button footer = new Button(this);
- footer.setText("Footer View " + i);
- expandableListView.addFooterView(footer);
- }
-
- // Set adapter here AFTER we set header and footer views
- setAdapter(expandableListView);
- }
-
- /**
- * @return The number of headers (and the same number of footers)
- */
- public int getNumOfHeadersAndFooters() {
- return sNumOfHeadersAndFooters;
- }
-
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java
deleted file mode 100644
index 64a0fff..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2007 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 android.widget.expandablelistview;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.ListUtil;
-import android.view.KeyEvent;
-import android.widget.ExpandableListView;
-
-public class ExpandableListWithHeadersTest extends
- ActivityInstrumentationTestCase2<ExpandableListWithHeaders> {
- private ExpandableListView mExpandableListView;
- private ListUtil mListUtil;
-
- public ExpandableListWithHeadersTest() {
- super(ExpandableListWithHeaders.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mExpandableListView = getActivity().getExpandableListView();
- mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
- }
-
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mExpandableListView);
- }
-
- @MediumTest
- public void testExpandOnFirstPosition() {
- // Should be a header, and hence the first group should NOT have expanded
- mListUtil.arrowScrollToSelectedPosition(0);
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertFalse(mExpandableListView.isGroupExpanded(0));
- }
-
- @LargeTest
- public void testExpandOnFirstGroup() {
- mListUtil.arrowScrollToSelectedPosition(getActivity().getNumOfHeadersAndFooters());
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertTrue(mExpandableListView.isGroupExpanded(0));
- }
-
- @MediumTest
- public void testContextMenus() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testContextMenus();
- }
-
- @MediumTest
- public void testConvertionBetweenFlatAndPacked() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testConvertionBetweenFlatAndPackedOnGroups();
- tester.testConvertionBetweenFlatAndPackedOnChildren();
- }
-
- @MediumTest
- public void testSelectedPosition() {
- ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
- tester.testSelectedPositionOnGroups();
- tester.testSelectedPositionOnChildren();
- }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java b/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java
deleted file mode 100644
index f4c9d56..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2007 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 android.widget.expandablelistview;
-
-import com.android.frameworks.coretests.R;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.TextView;
-
-public class InflatedExpandableListView extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.inflated_expandablelistview);
-
- ExpandableListView elv = (ExpandableListView) findViewById(R.id.elv);
- elv.setAdapter(new MyExpandableListAdapter());
- }
-
- public class MyExpandableListAdapter extends BaseExpandableListAdapter {
- // Sample data set. children[i] contains the children (String[]) for groups[i].
- private String[] groups = { "People Names", "Dog Names", "Cat Names", "Fish Names" };
- private String[][] children = {
- { "Arnold", "Barry", "Chuck", "David" },
- { "Ace", "Bandit", "Cha-Cha", "Deuce" },
- { "Fluffy", "Snuggles" },
- { "Goldy", "Bubbles" }
- };
-
- public Object getChild(int groupPosition, int childPosition) {
- return children[groupPosition][childPosition];
- }
-
- public long getChildId(int groupPosition, int childPosition) {
- return childPosition;
- }
-
- public int getChildrenCount(int groupPosition) {
- return children[groupPosition].length;
- }
-
- public TextView getGenericView() {
- // Layout parameters for the ExpandableListView
- AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, 64);
-
- TextView textView = new TextView(InflatedExpandableListView.this);
- textView.setLayoutParams(lp);
- // Center the text vertically
- textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
- // Set the text starting position
- textView.setPadding(36, 0, 0, 0);
- return textView;
- }
-
- public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
- View convertView, ViewGroup parent) {
- TextView textView = getGenericView();
- textView.setText(getChild(groupPosition, childPosition).toString());
- return textView;
- }
-
- public Object getGroup(int groupPosition) {
- return groups[groupPosition];
- }
-
- public int getGroupCount() {
- return groups.length;
- }
-
- public long getGroupId(int groupPosition) {
- return groupPosition;
- }
-
- public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
- ViewGroup parent) {
- TextView textView = getGenericView();
- textView.setText(getGroup(groupPosition).toString());
- return textView;
- }
-
- public boolean isChildSelectable(int groupPosition, int childPosition) {
- return true;
- }
-
- public boolean hasStableIds() {
- return true;
- }
-
- }
-
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java b/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java
deleted file mode 100644
index 2dbdff8..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.widget.expandablelistview;
-
-import android.view.ContextMenu;
-import android.view.View;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View.OnCreateContextMenuListener;
-import android.widget.ExpandableListView;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-
-public class PositionTesterContextMenuListener implements OnCreateContextMenuListener {
-
- private int groupPosition, childPosition;
-
- // Fake constant to store in testType a test type specific to headers and footers
- private static final int ADAPTER_TYPE = -1;
- private int testType; // as returned by getPackedPositionType
-
- // Will be set to null by each call to onCreateContextMenu, unless an error occurred.
- private String errorMessage;
-
- public void expectGroupContextMenu(int groupPosition) {
- this.groupPosition = groupPosition;
- testType = ExpandableListView.PACKED_POSITION_TYPE_GROUP;
- }
-
- public void expectChildContextMenu(int groupPosition, int childPosition) {
- this.groupPosition = groupPosition;
- this.childPosition = childPosition;
- testType = ExpandableListView.PACKED_POSITION_TYPE_CHILD;
- }
-
- public void expectAdapterContextMenu(int flatPosition) {
- this.groupPosition = flatPosition;
- testType = ADAPTER_TYPE;
- }
-
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- errorMessage = null;
- if (testType == ADAPTER_TYPE) {
- if (!isTrue("MenuInfo is not an AdapterContextMenuInfo",
- menuInfo instanceof AdapterContextMenuInfo)) {
- return;
- }
- AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo;
- if (!areEqual("Wrong flat position", groupPosition, adapterContextMenuInfo.position)) {
- return;
- }
- } else {
- if (!isTrue("MenuInfo is not an ExpandableListContextMenuInfo",
- menuInfo instanceof ExpandableListView.ExpandableListContextMenuInfo)) {
- return;
- }
- ExpandableListView.ExpandableListContextMenuInfo elvMenuInfo =
- (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
- long packedPosition = elvMenuInfo.packedPosition;
-
- int packedPositionType = ExpandableListView.getPackedPositionType(packedPosition);
- if (!areEqual("Wrong packed position type", testType, packedPositionType)) {
- return;
- }
-
- int packedPositionGroup = ExpandableListView.getPackedPositionGroup(packedPosition);
- if (!areEqual("Wrong group position", groupPosition, packedPositionGroup)) {
- return;
- }
-
- if (testType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
- int packedPositionChild = ExpandableListView.getPackedPositionChild(packedPosition);
- if (!areEqual("Wrong child position", childPosition, packedPositionChild)) {
- return;
- }
- }
- }
- }
-
- private boolean areEqual(String message, int expected, int actual) {
- if (expected != actual) {
- errorMessage = String.format(message + " (%d vs %d", expected, actual);
- return false;
- }
- return true;
- }
-
- private boolean isTrue(String message, boolean value) {
- if (!value) {
- errorMessage = message;
- return false;
- }
- return true;
- }
-
- public String getErrorMessage() {
- return errorMessage;
- }
-}
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index 3904234..0e405c2 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -53,7 +53,10 @@
/** [Sa * Da, Sc * Dc] */
MULTIPLY (14),
/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
- SCREEN (15);
+ SCREEN (15),
+ /** Saturate(S + D) */
+ ADD (16),
+ OVERLAY (17);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 6a7b2d1..4c1d243 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -16,21 +16,30 @@
package android.graphics.drawable;
-import java.io.InputStream;
-import java.io.IOException;
-import java.util.Arrays;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.NinePatch;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Region;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.StateSet;
-import android.util.Xml;
import android.util.TypedValue;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
/**
* A Drawable is a general abstraction for "something that can be drawn." Most
@@ -645,6 +654,8 @@
* Calling this method on a mutable Drawable will have no effect.
*
* @return This drawable.
+ * @see ConstantState
+ * @see #getConstantState()
*/
public Drawable mutate() {
return this;
@@ -750,6 +761,8 @@
drawable = new StateListDrawable();
} else if (name.equals("level-list")) {
drawable = new LevelListDrawable();
+ } else if (name.equals("mipmap")) {
+ drawable = new MipmapDrawable();
} else if (name.equals("layer-list")) {
drawable = new LayerDrawable();
} else if (name.equals("transition")) {
@@ -771,7 +784,7 @@
} else if (name.equals("inset")) {
drawable = new InsetDrawable();
} else if (name.equals("bitmap")) {
- drawable = new BitmapDrawable();
+ drawable = new BitmapDrawable(r);
if (r != null) {
((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
}
@@ -806,6 +819,9 @@
return null;
}
+ /**
+ * Inflate this Drawable from an XML resource.
+ */
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
@@ -814,6 +830,12 @@
a.recycle();
}
+ /**
+ * Inflate a Drawable from an XML resource.
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
void inflateWithAttributes(Resources r, XmlPullParser parser,
TypedArray attrs, int visibleAttr)
throws XmlPullParserException, IOException {
@@ -821,12 +843,27 @@
mVisible = attrs.getBoolean(visibleAttr, mVisible);
}
+ /**
+ * This abstract class is used by {@link Drawable}s to store shared constant state and data
+ * between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
+ * share a unique bitmap stored in their ConstantState.
+ *
+ * <p>
+ * {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
+ * from this ConstantState.
+ * </p>
+ *
+ * Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
+ * {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
+ * Drawable.
+ */
public static abstract class ConstantState {
/**
* Create a new drawable without supplying resources the caller
* is running in. Note that using this means the density-dependent
* drawables (like bitmaps) will not be able to update their target
- * density correctly.
+ * density correctly. One should use {@link #newDrawable(Resources)}
+ * instead to provide a resource.
*/
public abstract Drawable newDrawable();
/**
@@ -845,6 +882,13 @@
public abstract int getChangingConfigurations();
}
+ /**
+ * Return a {@link ConstantState} instance that holds the shared state of this Drawable.
+ *q
+ * @return The ConstantState associated to that Drawable.
+ * @see ConstantState
+ * @see Drawable#mutate()
+ */
public ConstantState getConstantState() {
return null;
}
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index c6f57d4..124d907 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -17,8 +17,16 @@
package android.graphics.drawable;
import android.content.res.Resources;
-import android.graphics.*;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+/**
+ * A helper class that contains several {@link Drawable}s and selects which one to use.
+ *
+ * You can subclass it to create your own DrawableContainers or directly use one its child classes.
+ */
public class DrawableContainer extends Drawable implements Drawable.Callback {
/**
@@ -196,8 +204,7 @@
mDrawableContainerState.getOpacity();
}
- public boolean selectDrawable(int idx)
- {
+ public boolean selectDrawable(int idx) {
if (idx == mCurIndex) {
return false;
}
@@ -255,6 +262,12 @@
return this;
}
+ /**
+ * A ConstantState that can contain several {@link Drawable}s.
+ *
+ * This class was made public to enable testing, and its visibility may change in a future
+ * release.
+ */
public abstract static class DrawableContainerState extends ConstantState {
final DrawableContainer mOwner;
@@ -443,12 +456,12 @@
return mConstantMinimumHeight;
}
- private void computeConstantSize() {
+ protected void computeConstantSize() {
mComputedConstantSize = true;
final int N = getChildCount();
final Drawable[] drawables = mDrawables;
- mConstantWidth = mConstantHeight = 0;
+ mConstantWidth = mConstantHeight = -1;
mConstantMinimumWidth = mConstantMinimumHeight = 0;
for (int i = 0; i < N; i++) {
Drawable dr = drawables[i];
diff --git a/graphics/java/android/graphics/drawable/MipmapDrawable.java b/graphics/java/android/graphics/drawable/MipmapDrawable.java
new file mode 100644
index 0000000..75fdeed
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/MipmapDrawable.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.graphics.drawable;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+/**
+ * A resource that manages a number of alternate Drawables, and which actually draws the one which
+ * size matches the most closely the drawing bounds. Providing several pre-scaled version of the
+ * drawable helps minimizing the aliasing artifacts that can be introduced by the scaling.
+ *
+ * <p>
+ * Use {@link #addDrawable(Drawable)} to define the different Drawables that will represent the
+ * mipmap levels of this MipmapDrawable. The mipmap Drawable that will actually be used when this
+ * MipmapDrawable is drawn is the one which has the smallest intrinsic height greater or equal than
+ * the bounds' height. This selection ensures that the best available mipmap level is scaled down to
+ * draw this MipmapDrawable.
+ * </p>
+ *
+ * If the bounds' height is larger than the largest mipmap, the largest mipmap will be scaled up.
+ * Note that Drawables without intrinsic height (i.e. with a negative value, such as Color) will
+ * only be used if no other mipmap Drawable are provided. The Drawables' intrinsic heights should
+ * not be changed after the Drawable has been added to this MipmapDrawable.
+ *
+ * <p>
+ * The different mipmaps' parameters (opacity, padding, color filter, gravity...) should typically
+ * be similar to ensure a continuous visual appearance when the MipmapDrawable is scaled. The aspect
+ * ratio of the different mipmaps should especially be equal.
+ * </p>
+ *
+ * A typical example use of a MipmapDrawable would be for an image which is intended to be scaled at
+ * various sizes, and for which one wants to provide pre-scaled versions to precisely control its
+ * appearance.
+ *
+ * <p>
+ * The intrinsic size of a MipmapDrawable are inferred from those of the largest mipmap (in terms of
+ * {@link Drawable#getIntrinsicHeight()}). On the opposite, its minimum
+ * size is defined by the smallest provided mipmap.
+ * </p>
+
+ * It can be defined in an XML file with the <code><mipmap></code> element.
+ * Each mipmap Drawable is defined in a nested <code><item></code>. For example:
+ * <pre>
+ * <mipmap xmlns:android="http://schemas.android.com/apk/res/android">
+ * <item android:drawable="@drawable/my_image_8" />
+ * <item android:drawable="@drawable/my_image_32" />
+ * <item android:drawable="@drawable/my_image_128" />
+ * </mipmap>
+ *</pre>
+ * <p>
+ * With this XML saved into the res/drawable/ folder of the project, it can be referenced as
+ * the drawable for an {@link android.widget.ImageView}. Assuming that the heights of the provided
+ * drawables are respectively 8, 32 and 128 pixels, the first one will be scaled down when the
+ * bounds' height is lower or equal than 8 pixels. The second drawable will then be used up to a
+ * height of 32 pixels and the largest drawable will be used for greater heights.
+ * </p>
+ * @attr ref android.R.styleable#MipmapDrawableItem_drawable
+ */
+public class MipmapDrawable extends DrawableContainer {
+ private final MipmapContainerState mMipmapContainerState;
+ private boolean mMutated;
+
+ public MipmapDrawable() {
+ this(null, null);
+ }
+
+ /**
+ * Adds a Drawable to the list of available mipmap Drawables. The Drawable actually used when
+ * this MipmapDrawable is drawn is determined from its bounds.
+ *
+ * This method has no effect if drawable is null.
+ *
+ * @param drawable The Drawable that will be added to list of available mipmap Drawables.
+ */
+
+ public void addDrawable(Drawable drawable) {
+ if (drawable != null) {
+ mMipmapContainerState.addDrawable(drawable);
+ onDrawableAdded();
+ }
+ }
+
+ private void onDrawableAdded() {
+ // selectDrawable assumes that the container content does not change.
+ // When a Drawable is added, the same index can correspond to a new Drawable, and since
+ // selectDrawable has a fast exit case when oldIndex==newIndex, the new drawable could end
+ // up not being used in place of the previous one if they happen to share the same index.
+ // This make sure the new computed index can actually replace the previous one.
+ selectDrawable(-1);
+ onBoundsChange(getBounds());
+ }
+
+ // overrides from Drawable
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ final int index = mMipmapContainerState.indexForBounds(bounds);
+
+ // Will call invalidateSelf() if needed
+ selectDrawable(index);
+
+ super.onBoundsChange(bounds);
+ }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+
+ super.inflate(r, parser, attrs);
+
+ int type;
+
+ final int innerDepth = parser.getDepth() + 1;
+ int depth;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth
+ || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (depth > innerDepth || !parser.getName().equals("item")) {
+ continue;
+ }
+
+ TypedArray a = r.obtainAttributes(attrs,
+ com.android.internal.R.styleable.MipmapDrawableItem);
+
+ int drawableRes = a.getResourceId(
+ com.android.internal.R.styleable.MipmapDrawableItem_drawable, 0);
+
+ a.recycle();
+
+ Drawable dr;
+ if (drawableRes != 0) {
+ dr = r.getDrawable(drawableRes);
+ } else {
+ while ((type = parser.next()) == XmlPullParser.TEXT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription()
+ + ": <item> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ dr = Drawable.createFromXmlInner(r, parser, attrs);
+ }
+
+ mMipmapContainerState.addDrawable(dr);
+ }
+
+ onDrawableAdded();
+ }
+
+ @Override
+ public Drawable mutate() {
+ if (!mMutated && super.mutate() == this) {
+ mMipmapContainerState.mMipmapHeights = mMipmapContainerState.mMipmapHeights.clone();
+ mMutated = true;
+ }
+ return this;
+ }
+
+ private final static class MipmapContainerState extends DrawableContainerState {
+ private int[] mMipmapHeights;
+
+ MipmapContainerState(MipmapContainerState orig, MipmapDrawable owner, Resources res) {
+ super(orig, owner, res);
+
+ if (orig != null) {
+ mMipmapHeights = orig.mMipmapHeights;
+ } else {
+ mMipmapHeights = new int[getChildren().length];
+ }
+
+ // Change the default value
+ setConstantSize(true);
+ }
+
+ /**
+ * Returns the index of the child mipmap drawable that will best fit the provided bounds.
+ * This index is determined by comparing bounds' height and children intrinsic heights.
+ * The returned mipmap index is the smallest mipmap which height is greater or equal than
+ * the bounds' height. If the bounds' height is larger than the largest mipmap, the largest
+ * mipmap index is returned.
+ *
+ * @param bounds The bounds of the MipMapDrawable.
+ * @return The index of the child Drawable that will best fit these bounds, or -1 if there
+ * are no children mipmaps.
+ */
+ public int indexForBounds(Rect bounds) {
+ final int boundsHeight = bounds.height();
+ final int N = getChildCount();
+ for (int i = 0; i < N; i++) {
+ if (boundsHeight <= mMipmapHeights[i]) {
+ return i;
+ }
+ }
+
+ // No mipmap larger than bounds found. Use largest one which will be scaled up.
+ if (N > 0) {
+ return N - 1;
+ }
+ // No Drawable mipmap at all
+ return -1;
+ }
+
+ /**
+ * Adds a Drawable to the list of available mipmap Drawables. This list can be retrieved
+ * using {@link DrawableContainer.DrawableContainerState#getChildren()} and this method
+ * ensures that it is always sorted by increasing {@link Drawable#getIntrinsicHeight()}.
+ *
+ * @param drawable The Drawable that will be added to children list
+ */
+ public void addDrawable(Drawable drawable) {
+ // Insert drawable in last position, correctly resetting cached values and
+ // especially mComputedConstantSize
+ int pos = addChild(drawable);
+
+ // Bubble sort the last drawable to restore the sort by intrinsic height
+ final int drawableHeight = drawable.getIntrinsicHeight();
+
+ while (pos > 0) {
+ final Drawable previousDrawable = mDrawables[pos-1];
+ final int previousIntrinsicHeight = previousDrawable.getIntrinsicHeight();
+
+ if (drawableHeight < previousIntrinsicHeight) {
+ mDrawables[pos] = previousDrawable;
+ mMipmapHeights[pos] = previousIntrinsicHeight;
+
+ mDrawables[pos-1] = drawable;
+ mMipmapHeights[pos-1] = drawableHeight;
+ pos--;
+ } else {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Intrinsic sizes are those of the largest available mipmap.
+ * Minimum sizes are those of the smallest available mipmap.
+ */
+ @Override
+ protected void computeConstantSize() {
+ final int N = getChildCount();
+ if (N > 0) {
+ final Drawable smallestDrawable = mDrawables[0];
+ mConstantMinimumWidth = smallestDrawable.getMinimumWidth();
+ mConstantMinimumHeight = smallestDrawable.getMinimumHeight();
+
+ final Drawable largestDrawable = mDrawables[N-1];
+ mConstantWidth = largestDrawable.getIntrinsicWidth();
+ mConstantHeight = largestDrawable.getIntrinsicHeight();
+ } else {
+ mConstantWidth = mConstantHeight = -1;
+ mConstantMinimumWidth = mConstantMinimumHeight = 0;
+ }
+ mComputedConstantSize = true;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new MipmapDrawable(this, null);
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res) {
+ return new MipmapDrawable(this, res);
+ }
+
+ @Override
+ public void growArray(int oldSize, int newSize) {
+ super.growArray(oldSize, newSize);
+ int[] newInts = new int[newSize];
+ System.arraycopy(mMipmapHeights, 0, newInts, 0, oldSize);
+ mMipmapHeights = newInts;
+ }
+ }
+
+ private MipmapDrawable(MipmapContainerState state, Resources res) {
+ MipmapContainerState as = new MipmapContainerState(state, this, res);
+ mMipmapContainerState = as;
+ setConstantState(as);
+ onDrawableAdded();
+ }
+}
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 17c0778..d32a0b5 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -76,10 +76,30 @@
subData1D(0, mType.getElementCount(), d);
}
+ public void subData(int off, FieldPacker fp) {
+ int eSize = mType.mElement.getSizeBytes();
+ final byte[] data = fp.getData();
+
+ int count = data.length / eSize;
+ if ((eSize * count) != data.length) {
+ throw new IllegalArgumentException("Field packer length " + data.length +
+ " not divisible by element size " + eSize + ".");
+ }
+ data1DChecks(off, count, data.length, data.length);
+ mRS.nAllocationSubData1D(mID, off, count, data, data.length);
+ }
+
private void data1DChecks(int off, int count, int len, int dataSize) {
mRS.validate();
- if((off < 0) || (count < 1) || ((off + count) > mType.getElementCount())) {
- throw new IllegalArgumentException("Offset or Count out of bounds.");
+ if(off < 0) {
+ throw new IllegalArgumentException("Offset must be >= 0.");
+ }
+ if(count < 1) {
+ throw new IllegalArgumentException("Count must be >= 1.");
+ }
+ if((off + count) > mType.getElementCount()) {
+ throw new IllegalArgumentException("Overflow, Available count " + mType.getElementCount() +
+ ", got " + count + " at offset " + off + ".");
}
if((len) < dataSize) {
throw new IllegalArgumentException("Array too small for allocation type.");
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte2.java
index 567d57fa..95cf88c 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte2.java
@@ -24,12 +24,12 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Byte2 {
+ public Byte2() {
}
- public float x;
- public float y;
+ public byte x;
+ public byte y;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte3.java
index 567d57fa..a6c0ca9 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte3.java
@@ -24,12 +24,13 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Byte3 {
+ public Byte3() {
}
- public float x;
- public float y;
+ public byte x;
+ public byte y;
+ public byte z;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte4.java
index 567d57fa..a5bfc61 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte4.java
@@ -24,14 +24,15 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Byte4 {
+ public Byte4() {
}
- public float x;
- public float y;
+ public byte x;
+ public byte y;
+ public byte z;
+ public byte w;
}
-
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index 10ef05a..7b155fe 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -126,6 +126,77 @@
return rs.mElement_USER_F32;
}
+ public static Element USER_ELEMENT(RenderScript rs) {
+ if(rs.mElement_USER_ELEMENT == null) {
+ rs.mElement_USER_ELEMENT = createUser(rs, DataType.RS_ELEMENT);
+ }
+ return rs.mElement_USER_ELEMENT;
+ }
+
+ public static Element USER_TYPE(RenderScript rs) {
+ if(rs.mElement_USER_TYPE == null) {
+ rs.mElement_USER_TYPE = createUser(rs, DataType.RS_TYPE);
+ }
+ return rs.mElement_USER_TYPE;
+ }
+
+ public static Element USER_ALLOCATION(RenderScript rs) {
+ if(rs.mElement_USER_ALLOCATION == null) {
+ rs.mElement_USER_ALLOCATION = createUser(rs, DataType.RS_ALLOCATION);
+ }
+ return rs.mElement_USER_ALLOCATION;
+ }
+
+ public static Element USER_SAMPLER(RenderScript rs) {
+ if(rs.mElement_USER_SAMPLER == null) {
+ rs.mElement_USER_SAMPLER = createUser(rs, DataType.RS_SAMPLER);
+ }
+ return rs.mElement_USER_SAMPLER;
+ }
+
+ public static Element USER_SCRIPT(RenderScript rs) {
+ if(rs.mElement_USER_SCRIPT == null) {
+ rs.mElement_USER_SCRIPT = createUser(rs, DataType.RS_SCRIPT);
+ }
+ return rs.mElement_USER_SCRIPT;
+ }
+
+ public static Element USER_MESH(RenderScript rs) {
+ if(rs.mElement_USER_MESH == null) {
+ rs.mElement_USER_MESH = createUser(rs, DataType.RS_MESH);
+ }
+ return rs.mElement_USER_MESH;
+ }
+
+ public static Element USER_PROGRAM_FRAGMENT(RenderScript rs) {
+ if(rs.mElement_USER_PROGRAM_FRAGMENT == null) {
+ rs.mElement_USER_PROGRAM_FRAGMENT = createUser(rs, DataType.RS_PROGRAM_FRAGMENT);
+ }
+ return rs.mElement_USER_PROGRAM_FRAGMENT;
+ }
+
+ public static Element USER_PROGRAM_VERTEX(RenderScript rs) {
+ if(rs.mElement_USER_PROGRAM_VERTEX == null) {
+ rs.mElement_USER_PROGRAM_VERTEX = createUser(rs, DataType.RS_PROGRAM_VERTEX);
+ }
+ return rs.mElement_USER_PROGRAM_VERTEX;
+ }
+
+ public static Element USER_PROGRAM_RASTER(RenderScript rs) {
+ if(rs.mElement_USER_PROGRAM_RASTER == null) {
+ rs.mElement_USER_PROGRAM_RASTER = createUser(rs, DataType.RS_PROGRAM_RASTER);
+ }
+ return rs.mElement_USER_PROGRAM_RASTER;
+ }
+
+ public static Element USER_PROGRAM_STORE(RenderScript rs) {
+ if(rs.mElement_USER_PROGRAM_STORE == null) {
+ rs.mElement_USER_PROGRAM_STORE = createUser(rs, DataType.RS_PROGRAM_STORE);
+ }
+ return rs.mElement_USER_PROGRAM_STORE;
+ }
+
+
public static Element A_8(RenderScript rs) {
if(rs.mElement_A_8 == null) {
rs.mElement_A_8 = createPixel(rs, DataType.UNSIGNED_8, DataKind.PIXEL_A);
diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java
index b26e47d..6d55c7e 100644
--- a/graphics/java/android/renderscript/FieldPacker.java
+++ b/graphics/java/android/renderscript/FieldPacker.java
@@ -33,21 +33,24 @@
}
}
- void reset() {
+ public void reset() {
mPos = 0;
}
+ public void reset(int i) {
+ mPos = i;
+ }
- void addI8(byte v) {
+ public void addI8(byte v) {
mData[mPos++] = v;
}
- void addI16(short v) {
+ public void addI16(short v) {
align(2);
mData[mPos++] = (byte)(v & 0xff);
mData[mPos++] = (byte)(v >> 8);
}
- void addI32(int v) {
+ public void addI32(int v) {
align(4);
mData[mPos++] = (byte)(v & 0xff);
mData[mPos++] = (byte)((v >> 8) & 0xff);
@@ -55,7 +58,7 @@
mData[mPos++] = (byte)((v >> 24) & 0xff);
}
- void addI64(long v) {
+ public void addI64(long v) {
align(8);
mData[mPos++] = (byte)(v & 0xff);
mData[mPos++] = (byte)((v >> 8) & 0xff);
@@ -67,14 +70,14 @@
mData[mPos++] = (byte)((v >> 56) & 0xff);
}
- void addU8(short v) {
+ public void addU8(short v) {
if ((v < 0) || (v > 0xff)) {
throw new IllegalArgumentException("Saving value out of range for type");
}
mData[mPos++] = (byte)v;
}
- void addU16(int v) {
+ public void addU16(int v) {
if ((v < 0) || (v > 0xffff)) {
throw new IllegalArgumentException("Saving value out of range for type");
}
@@ -83,7 +86,7 @@
mData[mPos++] = (byte)(v >> 8);
}
- void addU32(long v) {
+ public void addU32(long v) {
if ((v < 0) || (v > 0xffffffff)) {
throw new IllegalArgumentException("Saving value out of range for type");
}
@@ -94,7 +97,7 @@
mData[mPos++] = (byte)((v >> 24) & 0xff);
}
- void addU64(long v) {
+ public void addU64(long v) {
if (v < 0) {
throw new IllegalArgumentException("Saving value out of range for type");
}
@@ -109,15 +112,135 @@
mData[mPos++] = (byte)((v >> 56) & 0xff);
}
- void addF32(float v) {
+ public void addF32(float v) {
addI32(Float.floatToRawIntBits(v));
}
- void addF64(float v) {
+ public void addF64(float v) {
addI64(Double.doubleToRawLongBits(v));
}
- final byte[] getData() {
+ public void addObj(BaseObj obj) {
+ if (obj != null) {
+ addI32(obj.getID());
+ } else {
+ addI32(0);
+ }
+ }
+
+ public void addF32(Float2 v) {
+ addF32(v.x);
+ addF32(v.y);
+ }
+ public void addF32(Float3 v) {
+ addF32(v.x);
+ addF32(v.y);
+ addF32(v.z);
+ }
+ public void addF32(Float4 v) {
+ addF32(v.x);
+ addF32(v.y);
+ addF32(v.z);
+ addF32(v.w);
+ }
+
+ public void addI8(Byte2 v) {
+ addI8(v.x);
+ addI8(v.y);
+ }
+ public void addI8(Byte3 v) {
+ addI8(v.x);
+ addI8(v.y);
+ addI8(v.z);
+ }
+ public void addI8(Byte4 v) {
+ addI8(v.x);
+ addI8(v.y);
+ addI8(v.z);
+ addI8(v.w);
+ }
+
+ public void addU8(Short2 v) {
+ addU8(v.x);
+ addU8(v.y);
+ }
+ public void addU8(Short3 v) {
+ addU8(v.x);
+ addU8(v.y);
+ addU8(v.z);
+ }
+ public void addU8(Short4 v) {
+ addU8(v.x);
+ addU8(v.y);
+ addU8(v.z);
+ addU8(v.w);
+ }
+
+ public void addI16(Short2 v) {
+ addI16(v.x);
+ addI16(v.y);
+ }
+ public void addI16(Short3 v) {
+ addI16(v.x);
+ addI16(v.y);
+ addI16(v.z);
+ }
+ public void addI16(Short4 v) {
+ addI16(v.x);
+ addI16(v.y);
+ addI16(v.z);
+ addI16(v.w);
+ }
+
+ public void addU16(Int2 v) {
+ addU16(v.x);
+ addU16(v.y);
+ }
+ public void addU16(Int3 v) {
+ addU16(v.x);
+ addU16(v.y);
+ addU16(v.z);
+ }
+ public void addU16(Int4 v) {
+ addU16(v.x);
+ addU16(v.y);
+ addU16(v.z);
+ addU16(v.w);
+ }
+
+ public void addI32(Int2 v) {
+ addI32(v.x);
+ addI32(v.y);
+ }
+ public void addI32(Int3 v) {
+ addI32(v.x);
+ addI32(v.y);
+ addI32(v.z);
+ }
+ public void addI32(Int4 v) {
+ addI32(v.x);
+ addI32(v.y);
+ addI32(v.z);
+ addI32(v.w);
+ }
+
+ public void addU32(Int2 v) {
+ addU32(v.x);
+ addU32(v.y);
+ }
+ public void addU32(Int3 v) {
+ addU32(v.x);
+ addU32(v.y);
+ addU32(v.z);
+ }
+ public void addU32(Int4 v) {
+ addU32(v.x);
+ addU32(v.y);
+ addU32(v.z);
+ addU32(v.w);
+ }
+
+ public final byte[] getData() {
return mData;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Float2.java
similarity index 93%
rename from graphics/java/android/renderscript/Vector2f.java
rename to graphics/java/android/renderscript/Float2.java
index 567d57fa..8fea91f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Float2.java
@@ -24,8 +24,8 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Float2 {
+ public Float2() {
}
public float x;
diff --git a/graphics/java/android/renderscript/Vector3f.java b/graphics/java/android/renderscript/Float3.java
similarity index 94%
rename from graphics/java/android/renderscript/Vector3f.java
rename to graphics/java/android/renderscript/Float3.java
index f2842f3..9d9e406 100644
--- a/graphics/java/android/renderscript/Vector3f.java
+++ b/graphics/java/android/renderscript/Float3.java
@@ -24,8 +24,8 @@
* @hide
*
**/
-public class Vector3f {
- public Vector3f() {
+public class Float3 {
+ public Float3() {
}
public float x;
diff --git a/graphics/java/android/renderscript/Vector4f.java b/graphics/java/android/renderscript/Float4.java
similarity index 94%
rename from graphics/java/android/renderscript/Vector4f.java
rename to graphics/java/android/renderscript/Float4.java
index fabd959..a703e80 100644
--- a/graphics/java/android/renderscript/Vector4f.java
+++ b/graphics/java/android/renderscript/Float4.java
@@ -24,8 +24,8 @@
* @hide
*
**/
-public class Vector4f {
- public Vector4f() {
+public class Float4 {
+ public Float4() {
}
public float x;
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int2.java
index 567d57fa..56e2fe9 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int2.java
@@ -24,12 +24,12 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Int2 {
+ public Int2() {
}
- public float x;
- public float y;
+ public int x;
+ public int y;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int3.java
similarity index 88%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int3.java
index 567d57fa..1b27509 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int3.java
@@ -24,12 +24,13 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Int3 {
+ public Int3() {
}
- public float x;
- public float y;
+ public int x;
+ public int y;
+ public int z;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int4.java
similarity index 86%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int4.java
index 567d57fa..3d6f3f5 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int4.java
@@ -24,14 +24,15 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Int4 {
+ public Int4() {
}
- public float x;
- public float y;
+ public int x;
+ public int y;
+ public int z;
+ public int w;
}
-
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long2.java
index 567d57fa..11ead2f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long2.java
@@ -24,12 +24,12 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Long2 {
+ public Long2() {
}
- public float x;
- public float y;
+ public long x;
+ public long y;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long3.java
index 567d57fa..1604532 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long3.java
@@ -24,12 +24,13 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Long3 {
+ public Long3() {
}
- public float x;
- public float y;
+ public long x;
+ public long y;
+ public long z;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long4.java
index 567d57fa..2fd2747 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long4.java
@@ -24,14 +24,15 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Long4 {
+ public Long4() {
}
- public float x;
- public float y;
+ public long x;
+ public long y;
+ public long z;
+ public long w;
}
-
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index a935243..db2a3fd 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -203,6 +203,17 @@
Element mElement_USER_I32;
Element mElement_USER_F32;
+ Element mElement_USER_ELEMENT;
+ Element mElement_USER_TYPE;
+ Element mElement_USER_ALLOCATION;
+ Element mElement_USER_SAMPLER;
+ Element mElement_USER_SCRIPT;
+ Element mElement_USER_MESH;
+ Element mElement_USER_PROGRAM_FRAGMENT;
+ Element mElement_USER_PROGRAM_VERTEX;
+ Element mElement_USER_PROGRAM_RASTER;
+ Element mElement_USER_PROGRAM_STORE;
+
Element mElement_A_8;
Element mElement_RGB_565;
Element mElement_RGB_888;
diff --git a/graphics/java/android/renderscript/Script.java b/graphics/java/android/renderscript/Script.java
index 57ccfa3..0d21368 100644
--- a/graphics/java/android/renderscript/Script.java
+++ b/graphics/java/android/renderscript/Script.java
@@ -42,6 +42,10 @@
}
}
+ protected void invoke(int slot) {
+ mRS.nScriptInvoke(mID, slot);
+ }
+
Script(int id, RenderScript rs) {
super(rs);
mID = id;
@@ -145,5 +149,48 @@
}
+
+ public static class FieldBase {
+ protected Element mElement;
+ protected Type mType;
+ protected Allocation mAllocation;
+
+ protected void init(RenderScript rs, int dimx) {
+ mAllocation = Allocation.createSized(rs, mElement, dimx);
+ mType = mAllocation.getType();
+ }
+
+ protected FieldBase() {
+ }
+
+ public Element getElement() {
+ return mElement;
+ }
+
+ public Type getType() {
+ return mType;
+ }
+
+ public Allocation getAllocation() {
+ return mAllocation;
+ }
+
+ //@Override
+ public void updateAllocation() {
+ }
+
+
+ //
+ /*
+ public class ScriptField_UserField
+ extends android.renderscript.Script.FieldBase {
+
+ protected
+
+ }
+
+ */
+
+ }
}
diff --git a/graphics/java/android/renderscript/ScriptC.java b/graphics/java/android/renderscript/ScriptC.java
index bb99e23..f5d5b2f 100644
--- a/graphics/java/android/renderscript/ScriptC.java
+++ b/graphics/java/android/renderscript/ScriptC.java
@@ -37,6 +37,47 @@
super(id, rs);
}
+ protected ScriptC(RenderScript rs, Resources resources, int resourceID, boolean isRoot) {
+ super(0, rs);
+ mID = internalCreate(rs, resources, resourceID, isRoot);
+ }
+
+
+ private static synchronized int internalCreate(RenderScript rs, Resources resources, int resourceID, boolean isRoot) {
+ byte[] pgm;
+ int pgmLength;
+ InputStream is = resources.openRawResource(resourceID);
+ try {
+ try {
+ pgm = new byte[1024];
+ pgmLength = 0;
+ while(true) {
+ int bytesLeft = pgm.length - pgmLength;
+ if (bytesLeft == 0) {
+ byte[] buf2 = new byte[pgm.length * 2];
+ System.arraycopy(pgm, 0, buf2, 0, pgm.length);
+ pgm = buf2;
+ bytesLeft = pgm.length - pgmLength;
+ }
+ int bytesRead = is.read(pgm, pgmLength, bytesLeft);
+ if (bytesRead <= 0) {
+ break;
+ }
+ pgmLength += bytesRead;
+ }
+ } finally {
+ is.close();
+ }
+ } catch(IOException e) {
+ throw new Resources.NotFoundException();
+ }
+
+ rs.nScriptCBegin();
+ rs.nScriptCSetScript(pgm, 0, pgmLength);
+ rs.nScriptSetRoot(isRoot);
+ return rs.nScriptCCreate();
+ }
+
public static class Builder extends Script.Builder {
byte[] mProgram;
int mProgramLength;
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short2.java
index 567d57fa..426801f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short2.java
@@ -24,12 +24,12 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Short2 {
+ public Short2() {
}
- public float x;
- public float y;
+ public short x;
+ public short y;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short3.java
index 567d57fa..7b9c305 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short3.java
@@ -24,12 +24,13 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Short3 {
+ public Short3() {
}
- public float x;
- public float y;
+ public short x;
+ public short y;
+ public short z;
}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short4.java
index 567d57fa..9a474e2 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short4.java
@@ -24,14 +24,15 @@
* @hide
*
**/
-public class Vector2f {
- public Vector2f() {
+public class Short4 {
+ public Short4() {
}
- public float x;
- public float y;
+ public short x;
+ public short y;
+ public short z;
+ public short w;
}
-
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 98464a0..0b06022 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -76,16 +76,18 @@
LOCAL_SRC_FILES:= \
rsAdapter.cpp \
rsAllocation.cpp \
+ rsAnimation.cpp \
rsComponent.cpp \
rsContext.cpp \
rsDevice.cpp \
rsElement.cpp \
- rsFileA3D.cpp \
+ rsFileA3D.cpp \
rsLight.cpp \
rsLocklessFifo.cpp \
rsObjectBase.cpp \
rsMatrix.cpp \
- rsMesh.cpp \
+ rsMesh.cpp \
+ rsMutex.cpp \
rsNoise.cpp \
rsProgram.cpp \
rsProgramFragment.cpp \
@@ -96,7 +98,8 @@
rsScript.cpp \
rsScriptC.cpp \
rsScriptC_Lib.cpp \
- rsShaderCache.cpp \
+ rsShaderCache.cpp \
+ rsSignal.cpp \
rsSimpleMesh.cpp \
rsThreadIO.cpp \
rsType.cpp \
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index d280f50..7415ba9 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -30,6 +30,7 @@
typedef void * RsAdapter1D;
typedef void * RsAdapter2D;
typedef void * RsAllocation;
+typedef void * RsAnimation;
typedef void * RsContext;
typedef void * RsDevice;
typedef void * RsElement;
@@ -205,7 +206,27 @@
enum RsError {
RS_ERROR_NONE,
RS_ERROR_BAD_SHADER,
- RS_ERROR_BAD_SCRIPT
+ RS_ERROR_BAD_SCRIPT,
+ RS_ERROR_BAD_VALUE,
+ RS_ERROR_OUT_OF_MEMORY
+};
+
+enum RsAnimationInterpolation {
+ RS_ANIMATION_INTERPOLATION_STEP,
+ RS_ANIMATION_INTERPOLATION_LINEAR,
+ RS_ANIMATION_INTERPOLATION_BEZIER,
+ RS_ANIMATION_INTERPOLATION_CARDINAL,
+ RS_ANIMATION_INTERPOLATION_HERMITE,
+ RS_ANIMATION_INTERPOLATION_BSPLINE
+};
+
+enum RsAnimationEdge {
+ RS_ANIMATION_EDGE_UNDEFINED,
+ RS_ANIMATION_EDGE_CONSTANT,
+ RS_ANIMATION_EDGE_GRADIENT,
+ RS_ANIMATION_EDGE_CYCLE,
+ RS_ANIMATION_EDGE_OSCILLATE,
+ RS_ANIMATION_EDGE_CYLE_RELATIVE
};
#ifndef NO_RS_FUNCS
diff --git a/libs/rs/java/Fountain/res/raw/fountain2.rs b/libs/rs/java/Fountain/res/raw/fountain2.rs
index 3301140..5d36e35 100644
--- a/libs/rs/java/Fountain/res/raw/fountain2.rs
+++ b/libs/rs/java/Fountain/res/raw/fountain2.rs
@@ -1,9 +1,9 @@
// Fountain test script
#pragma version(1)
-#include "rs_types.rsh"
-#include "rs_math.rsh"
-#include "rs_graphics.rsh"
+#include "../../../../scriptc/rs_types.rsh"
+#include "../../../../scriptc/rs_math.rsh"
+#include "../../../../scriptc/rs_graphics.rsh"
static int newPart = 0;
@@ -12,15 +12,15 @@
int rate;
int count;
float r, g, b;
- rs_allocation partBuffer;
rs_mesh partMesh;
+ rs_allocation partBuffer;
} Control_t;
Control_t *Control;
typedef struct Point_s{
float2 delta;
- float2 position;
- unsigned int color;
+ rs_position2 pos;
+ rs_color4u color;
} Point_t;
Point_t *point;
@@ -33,8 +33,6 @@
if (rate) {
float rMax = ((float)rate) * 0.005f;
- int x = Control->x;
- int y = Control->y;
int color = ((int)(Control->r * 255.f)) |
((int)(Control->g * 255.f)) << 8 |
((int)(Control->b * 255.f)) << 16 |
@@ -42,9 +40,11 @@
Point_t * np = &p[newPart];
while (rate--) {
- np->delta = vec2Rand(rMax);
- np->position.x = x;
- np->position.y = y;
+ np->delta.x = rand(rMax);
+ np->delta.y = rand(rMax);
+ //np->delta = vec2Rand(rMax);
+ np->pos.x = Control->x;
+ np->pos.y = Control->y;
np->color = color;
newPart++;
np++;
@@ -57,13 +57,13 @@
for (ct=0; ct < count; ct++) {
float dy = p->delta.y + 0.15f;
- float posy = p->position.y + dy;
+ float posy = p->pos.y + dy;
if ((posy > height) && (dy > 0)) {
dy *= -0.3f;
}
p->delta.y = dy;
- p->position.x += p->delta.x;
- p->position.y = posy;
+ p->pos.x += p->delta.x;
+ p->pos.y = posy;
p++;
}
diff --git a/libs/rs/java/ImageProcessing/res/raw/threshold2.rs b/libs/rs/java/ImageProcessing/res/raw/threshold2.rs
new file mode 100644
index 0000000..9f687b5
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/res/raw/threshold2.rs
@@ -0,0 +1,49 @@
+#pragma version(1)
+
+#include "../../../../scriptc/rs_types.rsh"
+#include "../../../../scriptc/rs_math.rsh"
+#include "../../../../scriptc/rs_graphics.rsh"
+
+typedef struct Params_s{
+ int inHeight;
+ int inWidth;
+ int outHeight;
+ int outWidth;
+ float threshold;
+} Params_t;
+
+Params_t * Params;
+rs_color4u * InPixel;
+rs_color4u * OutPixel;
+
+
+int main() {
+ int t = uptimeMillis();
+
+ rs_color4u *in = InPixel;
+ rs_color4u *out = OutPixel;
+
+ int count = Params->inWidth * Params->inHeight;
+ int i;
+ float threshold = Params->threshold * 255.f;
+
+ for (i = 0; i < count; i++) {
+ float luminance = 0.2125f * in->x +
+ 0.7154f * in->y +
+ 0.0721f * in->z;
+ if (luminance > threshold) {
+ *out = *in;
+ } else {
+ *((int *)out) = *((int *)in) & 0xff000000;
+ }
+
+ in++;
+ out++;
+ }
+
+ t= uptimeMillis() - t;
+ debugI32("Filter time", t);
+
+ sendToClient(&count, 1, 4, 0);
+ return 0;
+}
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index cb9937c..08aa369 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -480,3 +480,13 @@
param uint32_t slot
}
+AnimationCreate {
+ param const float *inValues
+ param const float *outValues
+ param uint32_t valueCount
+ param RsAnimationInterpolation interp
+ param RsAnimationEdge pre
+ param RsAnimationEdge post
+ ret RsAnimation
+ }
+
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 4e8278d..e5ff1d7 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -170,6 +170,7 @@
glGenerateMipmap(GL_TEXTURE_2D);
}
+ rsc->checkError("Allocation::uploadToTexture");
}
void Allocation::deferedUploadToBufferObject(const Context *rsc)
@@ -201,6 +202,7 @@
glBindBuffer(GL_ARRAY_BUFFER, mBufferID);
glBufferData(GL_ARRAY_BUFFER, mType->getSizeBytes(), getPtr(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
+ rsc->checkError("Allocation::uploadToBufferObject");
}
void Allocation::uploadCheck(const Context *rsc)
diff --git a/libs/rs/rsAnimation.cpp b/libs/rs/rsAnimation.cpp
new file mode 100644
index 0000000..48c9334
--- /dev/null
+++ b/libs/rs/rsAnimation.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#include "rsContext.h"
+#include "rsAnimation.h"
+
+
+using namespace android;
+using namespace android::renderscript;
+
+/*
+Animation::Animation(Context *rsc) : ObjectBase(rsc)
+{
+ mAllocFile = __FILE__;
+ mAllocLine = __LINE__;
+
+ mValuesInput = NULL;
+ mValuesOutput = NULL;
+ mValueCount = 0;
+ mInterpolation = RS_ANIMATION_INTERPOLATION_STEP;
+ mEdgePre = RS_ANIMATION_EDGE_UNDEFINED;
+ mEdgePost = RS_ANIMATION_EDGE_UNDEFINED;
+ mInputMin = 0;
+ mInputMax = 0;
+}
+
+Animation * Animation::create(Context *rsc,
+ const float *inValues, const float *outValues,
+ uint32_t valueCount, RsAnimationInterpolation interp,
+ RsAnimationEdge pre, RsAnimationEdge post)
+{
+ if (valueCount < 2) {
+ rsc->setError(RS_ERROR_BAD_VALUE, "Animations require more than 2 values.");
+ return NULL;
+ }
+ Animation *a = new Animation(rsc);
+ if (!a) {
+ rsc->setError(RS_ERROR_OUT_OF_MEMORY);
+ return NULL;
+ }
+
+ float *vin = (float *)malloc(valueCount * sizeof(float));
+ float *vout = (float *)malloc(valueCount * sizeof(float));
+ a->mValuesInput = vin;
+ a->mValuesOutput = vout;
+ if (a->mValuesInput == NULL || a->mValuesOutput == NULL) {
+ delete a;
+ rsc->setError(RS_ERROR_OUT_OF_MEMORY);
+ return NULL;
+ }
+
+ a->mEdgePre = pre;
+ a->mEdgePost = post;
+ a->mInterpolation = interp;
+ a->mValueCount = valueCount;
+
+ memcpy(vin, inValues, valueCount * sizeof(float));
+ memcpy(vout, outValues, valueCount * sizeof(float));
+ a->mInputMin = inValues[0];
+ a->mInputMax = inValues[0];
+
+ bool needSort = false;
+ for (uint32_t ct=1; ct < valueCount; ct++) {
+ if (a->mInputMin > vin[ct]) {
+ needSort = true;
+ a->mInputMin = vin[ct];
+ }
+ if (a->mInputMax < vin[ct]) {
+ a->mInputMax = vin[ct];
+ } else {
+ needSort = true;
+ }
+ }
+
+ while (1) {
+ bool changed = false;
+ for (uint32_t ct=1; ct < valueCount; ct++) {
+ if (vin[ct-1] > vin[ct]) {
+ float t = vin[ct-1];
+ vin[ct-1] = vin[ct];
+ vin[ct] = t;
+ t = vout[ct-1];
+ vout[ct-1] = vout[ct];
+ vout[ct] = t;
+ changed = true;
+ }
+ }
+ if (!changed) break;
+ }
+
+ return a;
+}
+*/
+
+
+/////////////////////////////////////////
+//
+
+namespace android {
+namespace renderscript {
+
+RsAnimation rsi_AnimationCreate(Context *rsc,
+ const float *inValues,
+ const float *outValues,
+ uint32_t valueCount,
+ RsAnimationInterpolation interp,
+ RsAnimationEdge pre,
+ RsAnimationEdge post)
+{
+ //LOGE("rsi_ElementCreate %i %i %i %i", dt, dk, norm, vecSize);
+ Animation *a = NULL;//Animation::create(rsc, inValues, outValues, valueCount, interp, pre, post);
+ if (a != NULL) {
+ a->incUserRef();
+ }
+ return (RsAnimation)a;
+}
+
+
+}
+}
+
diff --git a/libs/rs/rsAnimation.h b/libs/rs/rsAnimation.h
new file mode 100644
index 0000000..b8db661
--- /dev/null
+++ b/libs/rs/rsAnimation.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#ifndef ANDROID_RS_ANIMATION_H
+#define ANDROID_RS_ANIMATION_H
+
+#include "rsUtils.h"
+#include "rsObjectBase.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+namespace renderscript {
+
+
+class Animation : public ObjectBase
+{
+public:
+ ~Animation();
+
+ static Animation * create(Context *rsc,
+ const float *inValues, const float *outValues,
+ uint32_t valueCount, RsAnimationInterpolation,
+ RsAnimationEdge pre, RsAnimationEdge post);
+
+ float eval(float) const;
+
+
+protected:
+ Animation(Context *rsc);
+
+
+
+ float evalInRange(float) const;
+
+
+
+ const float *mValuesInput;
+ const float *mValuesOutput;
+ uint32_t mValueCount;
+ RsAnimationInterpolation mInterpolation;
+ RsAnimationEdge mEdgePre;
+ RsAnimationEdge mEdgePost;
+
+ // derived
+ float mInputMin;
+ float mInputMax;
+};
+
+
+
+
+}
+}
+#endif //ANDROID_STRUCTURED_ELEMENT_H
+
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index d8a9a99..4107229 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -664,8 +664,7 @@
bool Context::objDestroyOOBInit()
{
- int status = pthread_mutex_init(&mObjDestroy.mMutex, NULL);
- if (status) {
+ if (!mObjDestroy.mMutex.init()) {
LOGE("Context::ObjDestroyOOBInit mutex init failure");
return false;
}
@@ -675,9 +674,8 @@
void Context::objDestroyOOBRun()
{
if (mObjDestroy.mNeedToEmpty) {
- int status = pthread_mutex_lock(&mObjDestroy.mMutex);
- if (status) {
- LOGE("Context::ObjDestroyOOBRun: error %i locking for OOBRun.", status);
+ if (!mObjDestroy.mMutex.lock()) {
+ LOGE("Context::ObjDestroyOOBRun: error locking for OOBRun.");
return;
}
@@ -686,35 +684,25 @@
}
mObjDestroy.mDestroyList.clear();
mObjDestroy.mNeedToEmpty = false;
-
- status = pthread_mutex_unlock(&mObjDestroy.mMutex);
- if (status) {
- LOGE("Context::ObjDestroyOOBRun: error %i unlocking for set condition.", status);
- }
+ mObjDestroy.mMutex.unlock();
}
}
void Context::objDestroyOOBDestroy()
{
rsAssert(!mObjDestroy.mNeedToEmpty);
- pthread_mutex_destroy(&mObjDestroy.mMutex);
}
void Context::objDestroyAdd(ObjectBase *obj)
{
- int status = pthread_mutex_lock(&mObjDestroy.mMutex);
- if (status) {
- LOGE("Context::ObjDestroyOOBRun: error %i locking for OOBRun.", status);
+ if (!mObjDestroy.mMutex.lock()) {
+ LOGE("Context::ObjDestroyOOBRun: error locking for OOBRun.");
return;
}
mObjDestroy.mNeedToEmpty = true;
mObjDestroy.mDestroyList.add(obj);
-
- status = pthread_mutex_unlock(&mObjDestroy.mMutex);
- if (status) {
- LOGE("Context::ObjDestroyOOBRun: error %i unlocking for set condition.", status);
- }
+ mObjDestroy.mMutex.unlock();
}
uint32_t Context::getMessageToClient(void *data, size_t *receiveLen, size_t bufferLen, bool wait)
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 82c3687..8e755a9 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -18,6 +18,7 @@
#define ANDROID_RS_CONTEXT_H
#include "rsUtils.h"
+#include "rsMutex.h"
#include "rsThreadIO.h"
#include "rsType.h"
@@ -161,7 +162,7 @@
void dumpDebug() const;
void checkError(const char *) const;
const char * getError(RsError *);
- void setError(RsError e, const char *msg);
+ void setError(RsError e, const char *msg = NULL);
mutable const ObjectBase * mObjHead;
@@ -227,7 +228,7 @@
struct ObjDestroyOOB {
- pthread_mutex_t mMutex;
+ Mutex mMutex;
Vector<ObjectBase *> mDestroyList;
bool mNeedToEmpty;
};
diff --git a/libs/rs/rsLocklessFifo.cpp b/libs/rs/rsLocklessFifo.cpp
index c796520..76ca32e 100644
--- a/libs/rs/rsLocklessFifo.cpp
+++ b/libs/rs/rsLocklessFifo.cpp
@@ -17,7 +17,7 @@
#include "rsLocklessFifo.h"
using namespace android;
-
+using namespace android::renderscript;
LocklessCommandFifo::LocklessCommandFifo()
{
@@ -128,15 +128,19 @@
//dumpState("flush 2");
}
+void LocklessCommandFifo::wait()
+{
+ while(isEmpty() && !mInShutdown) {
+ mSignalToControl.set();
+ mSignalToWorker.wait();
+ }
+}
+
const void * LocklessCommandFifo::get(uint32_t *command, uint32_t *bytesData)
{
while(1) {
//dumpState("get");
- while(isEmpty() && !mInShutdown) {
- mSignalToControl.set();
- mSignalToWorker.wait();
- }
-
+ wait();
if (mInShutdown) {
*command = 0;
*bytesData = 0;
@@ -192,79 +196,3 @@
LOGV("%s put %p, get %p, buf %p, end %p", s, mPut, mGet, mBuffer, mEnd);
}
-LocklessCommandFifo::Signal::Signal()
-{
- mSet = true;
-}
-
-LocklessCommandFifo::Signal::~Signal()
-{
- pthread_mutex_destroy(&mMutex);
- pthread_cond_destroy(&mCondition);
-}
-
-bool LocklessCommandFifo::Signal::init()
-{
- int status = pthread_mutex_init(&mMutex, NULL);
- if (status) {
- LOGE("LocklessFifo mutex init failure");
- return false;
- }
-
- status = pthread_cond_init(&mCondition, NULL);
- if (status) {
- LOGE("LocklessFifo condition init failure");
- pthread_mutex_destroy(&mMutex);
- return false;
- }
-
- return true;
-}
-
-void LocklessCommandFifo::Signal::set()
-{
- int status;
-
- status = pthread_mutex_lock(&mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i locking for set condition.", status);
- return;
- }
-
- mSet = true;
-
- status = pthread_cond_signal(&mCondition);
- if (status) {
- LOGE("LocklessCommandFifo: error %i on set condition.", status);
- }
-
- status = pthread_mutex_unlock(&mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i unlocking for set condition.", status);
- }
-}
-
-void LocklessCommandFifo::Signal::wait()
-{
- int status;
-
- status = pthread_mutex_lock(&mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i locking for condition.", status);
- return;
- }
-
- if (!mSet) {
- status = pthread_cond_wait(&mCondition, &mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i waiting on condition.", status);
- }
- }
- mSet = false;
-
- status = pthread_mutex_unlock(&mMutex);
- if (status) {
- LOGE("LocklessCommandFifo: error %i unlocking for condition.", status);
- }
-}
-
diff --git a/libs/rs/rsLocklessFifo.h b/libs/rs/rsLocklessFifo.h
index d0a4356..ae906ca 100644
--- a/libs/rs/rsLocklessFifo.h
+++ b/libs/rs/rsLocklessFifo.h
@@ -19,8 +19,10 @@
#include "rsUtils.h"
+#include "rsSignal.h"
namespace android {
+namespace renderscript {
// A simple FIFO to be used as a producer / consumer between two
@@ -37,24 +39,7 @@
LocklessCommandFifo();
~LocklessCommandFifo();
-
protected:
- class Signal {
- public:
- Signal();
- ~Signal();
-
- bool init();
-
- void set();
- void wait();
-
- protected:
- bool mSet;
- pthread_mutex_t mMutex;
- pthread_cond_t mCondition;
- };
-
uint8_t * volatile mPut;
uint8_t * volatile mGet;
uint8_t * mBuffer;
@@ -65,14 +50,14 @@
Signal mSignalToWorker;
Signal mSignalToControl;
-
-
public:
void * reserve(uint32_t bytes);
void commit(uint32_t command, uint32_t bytes);
void commitSync(uint32_t command, uint32_t bytes);
void flush();
+ void wait();
+
const void * get(uint32_t *command, uint32_t *bytesData);
void next();
@@ -88,4 +73,5 @@
}
+}
#endif
diff --git a/libs/rs/rsMutex.cpp b/libs/rs/rsMutex.cpp
new file mode 100644
index 0000000..37752f2
--- /dev/null
+++ b/libs/rs/rsMutex.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#include "rsMutex.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+
+Mutex::Mutex()
+{
+}
+
+Mutex::~Mutex()
+{
+ pthread_mutex_destroy(&mMutex);
+}
+
+bool Mutex::init()
+{
+ int status = pthread_mutex_init(&mMutex, NULL);
+ if (status) {
+ LOGE("Mutex::Mutex init failure");
+ return false;
+ }
+ return true;
+}
+
+bool Mutex::lock()
+{
+ int status;
+ status = pthread_mutex_lock(&mMutex);
+ if (status) {
+ LOGE("Mutex: error %i locking.", status);
+ return false;
+ }
+ return true;
+}
+
+bool Mutex::unlock()
+{
+ int status;
+ status = pthread_mutex_unlock(&mMutex);
+ if (status) {
+ LOGE("Mutex error %i unlocking.", status);
+ return false;
+ }
+ return true;
+}
+
+
diff --git a/graphics/java/android/renderscript/Vector2f.java b/libs/rs/rsMutex.h
similarity index 69%
copy from graphics/java/android/renderscript/Vector2f.java
copy to libs/rs/rsMutex.h
index 567d57fa..47725d7 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/libs/rs/rsMutex.h
@@ -14,24 +14,30 @@
* limitations under the License.
*/
-package android.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
+#ifndef ANDROID_RS_MUTEX_H
+#define ANDROID_RS_MUTEX_H
-/**
- * @hide
- *
- **/
-public class Vector2f {
- public Vector2f() {
- }
+#include "rsUtils.h"
- public float x;
- public float y;
+namespace android {
+namespace renderscript {
+
+class Mutex {
+public:
+ Mutex();
+ ~Mutex();
+
+ bool init();
+ bool lock();
+ bool unlock();
+
+protected:
+ pthread_mutex_t mMutex;
+};
+
+}
}
-
-
+#endif
diff --git a/libs/rs/rsSignal.cpp b/libs/rs/rsSignal.cpp
new file mode 100644
index 0000000..9239bfd
--- /dev/null
+++ b/libs/rs/rsSignal.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#include "rsSignal.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+
+Signal::Signal()
+{
+ mSet = true;
+}
+
+Signal::~Signal()
+{
+ pthread_mutex_destroy(&mMutex);
+ pthread_cond_destroy(&mCondition);
+}
+
+bool Signal::init()
+{
+ int status = pthread_mutex_init(&mMutex, NULL);
+ if (status) {
+ LOGE("LocklessFifo mutex init failure");
+ return false;
+ }
+
+ status = pthread_cond_init(&mCondition, NULL);
+ if (status) {
+ LOGE("LocklessFifo condition init failure");
+ pthread_mutex_destroy(&mMutex);
+ return false;
+ }
+
+ return true;
+}
+
+void Signal::set()
+{
+ int status;
+
+ status = pthread_mutex_lock(&mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i locking for set condition.", status);
+ return;
+ }
+
+ mSet = true;
+
+ status = pthread_cond_signal(&mCondition);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i on set condition.", status);
+ }
+
+ status = pthread_mutex_unlock(&mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i unlocking for set condition.", status);
+ }
+}
+
+void Signal::wait()
+{
+ int status;
+
+ status = pthread_mutex_lock(&mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i locking for condition.", status);
+ return;
+ }
+
+ if (!mSet) {
+ status = pthread_cond_wait(&mCondition, &mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i waiting on condition.", status);
+ }
+ }
+ mSet = false;
+
+ status = pthread_mutex_unlock(&mMutex);
+ if (status) {
+ LOGE("LocklessCommandFifo: error %i unlocking for condition.", status);
+ }
+}
+
diff --git a/graphics/java/android/renderscript/Vector2f.java b/libs/rs/rsSignal.h
similarity index 65%
copy from graphics/java/android/renderscript/Vector2f.java
copy to libs/rs/rsSignal.h
index 567d57fa..2e760f1 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/libs/rs/rsSignal.h
@@ -14,24 +14,33 @@
* limitations under the License.
*/
-package android.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
+#ifndef ANDROID_RS_SIGNAL_H
+#define ANDROID_RS_SIGNAL_H
-/**
- * @hide
- *
- **/
-public class Vector2f {
- public Vector2f() {
- }
+#include "rsUtils.h"
- public float x;
- public float y;
+namespace android {
+namespace renderscript {
+
+class Signal {
+public:
+ Signal();
+ ~Signal();
+
+ bool init();
+
+ void set();
+ void wait();
+
+protected:
+ bool mSet;
+ pthread_mutex_t mMutex;
+ pthread_cond_t mCondition;
+};
+
+}
}
-
-
+#endif
diff --git a/libs/rs/rsg_ScriptJavaClass.cpp b/libs/rs/rsg_ScriptJavaClass.cpp
index cee9f52..0169b98 100644
--- a/libs/rs/rsg_ScriptJavaClass.cpp
+++ b/libs/rs/rsg_ScriptJavaClass.cpp
@@ -7,8 +7,12 @@
struct Element;
struct ElementField {
+ // An Element Field is a combination of an Element with a name assigned.
+
const char *name;
Element *e;
+
+
ElementField(const char *n, Element *_e) {
name = n;
e = _e;
@@ -20,12 +24,21 @@
};
struct Element {
+ // An Element can take one of two forms.
+ // 1: Basic. It contains a single basic type and vector size.
+ // 2: Complex. It contains a list of fields with names. Each field
+ // will in turn be another element.
+
ElementField *fields;
- size_t fieldCount;
+ size_t fieldCount; // If field count is 0, the element is a Basic type.
const char *name;
bool generated;
+ // The basic data type from RenderScript.h
RsDataType compType;
+
+ // The vector size of the data type for float2, float3, ....
+ // Allowed sizes are 2,3,4,8,16
uint32_t compVectorSize;
Element() {
diff --git a/libs/rs/scriptc/rs_geom.rsh b/libs/rs/scriptc/rs_geom.rsh
new file mode 100644
index 0000000..6e9e9fc
--- /dev/null
+++ b/libs/rs/scriptc/rs_geom.rsh
@@ -0,0 +1,26 @@
+
+extern float3 __attribute__((overloadable)) cross(float3, float3);
+extern float4 __attribute__((overloadable)) cross(float4, float4);
+
+//extern float __attribute__((overloadable)) dot(float, float);
+extern float __attribute__((overloadable)) dot(float2, float2);
+extern float __attribute__((overloadable)) dot(float3, float3);
+extern float __attribute__((overloadable)) dot(float4, float4);
+
+//extern float __attribute__((overloadable)) distance(float, float);
+extern float __attribute__((overloadable)) distance(float2, float2);
+extern float __attribute__((overloadable)) distance(float3, float3);
+extern float __attribute__((overloadable)) distance(float4, float4);
+
+//extern float __attribute__((overloadable)) length(float);
+extern float __attribute__((overloadable)) length(float2);
+extern float __attribute__((overloadable)) length(float3);
+extern float __attribute__((overloadable)) length(float4);
+
+extern float2 __attribute__((overloadable)) normalize(float2);
+extern float3 __attribute__((overloadable)) normalize(float3);
+extern float4 __attribute__((overloadable)) normalize(float4);
+
+
+
+
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index 70cd562..0f03732 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -1,5 +1,7 @@
+extern float rand(float max);
+
extern float2 vec2Rand(float len);
extern float3 float3Norm(float3);
diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh
index 613c7ca..fba0f8f 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -1,264 +1,262 @@
// Float ops
extern float __attribute__((overloadable)) abs(float);
-extern float2 __attribute__((overloadable)) abs(float2);
-extern float3 __attribute__((overloadable)) abs(float3);
-extern float4 __attribute__((overloadable)) abs(float4);
-extern float8 __attribute__((overloadable)) abs(float8);
-extern float16 __attribute__((overloadable)) abs(float16);
+//extern float2 __attribute__((overloadable)) abs(float2);
+//extern float3 __attribute__((overloadable)) abs(float3);
+//extern float4 __attribute__((overloadable)) abs(float4);
+//extern float8 __attribute__((overloadable)) abs(float8);
+//extern float16 __attribute__((overloadable)) abs(float16);
extern float __attribute__((overloadable)) acos(float);
-extern float2 __attribute__((overloadable)) acos(float2);
-extern float3 __attribute__((overloadable)) acos(float3);
-extern float4 __attribute__((overloadable)) acos(float4);
-extern float8 __attribute__((overloadable)) acos(float8);
-extern float16 __attribute__((overloadable)) acos(float16);
+//extern float2 __attribute__((overloadable)) acos(float2);
+//extern float3 __attribute__((overloadable)) acos(float3);
+//extern float4 __attribute__((overloadable)) acos(float4);
+//extern float8 __attribute__((overloadable)) acos(float8);
+//extern float16 __attribute__((overloadable)) acos(float16);
extern float __attribute__((overloadable)) asin(float);
-extern float2 __attribute__((overloadable)) asin(float2);
-extern float3 __attribute__((overloadable)) asin(float3);
-extern float4 __attribute__((overloadable)) asin(float4);
-extern float8 __attribute__((overloadable)) asin(float8);
-extern float16 __attribute__((overloadable)) asin(float16);
+//extern float2 __attribute__((overloadable)) asin(float2);
+//extern float3 __attribute__((overloadable)) asin(float3);
+//extern float4 __attribute__((overloadable)) asin(float4);
+//extern float8 __attribute__((overloadable)) asin(float8);
+//extern float16 __attribute__((overloadable)) asin(float16);
extern float __attribute__((overloadable)) atan(float);
-extern float2 __attribute__((overloadable)) atan(float2);
-extern float3 __attribute__((overloadable)) atan(float3);
-extern float4 __attribute__((overloadable)) atan(float4);
-extern float8 __attribute__((overloadable)) atan(float8);
-extern float16 __attribute__((overloadable)) atan(float16);
+//extern float2 __attribute__((overloadable)) atan(float2);
+//extern float3 __attribute__((overloadable)) atan(float3);
+//extern float4 __attribute__((overloadable)) atan(float4);
+//extern float8 __attribute__((overloadable)) atan(float8);
+//extern float16 __attribute__((overloadable)) atan(float16);
extern float __attribute__((overloadable)) atan2(float, float);
-extern float2 __attribute__((overloadable)) atan2(float2, float2);
-extern float3 __attribute__((overloadable)) atan2(float3, float3);
-extern float4 __attribute__((overloadable)) atan2(float4, float4);
-extern float8 __attribute__((overloadable)) atan2(float8, float8);
-extern float16 __attribute__((overloadable)) atan2(float16, float16);
+//extern float2 __attribute__((overloadable)) atan2(float2, float2);
+//extern float3 __attribute__((overloadable)) atan2(float3, float3);
+//extern float4 __attribute__((overloadable)) atan2(float4, float4);
+//extern float8 __attribute__((overloadable)) atan2(float8, float8);
+//extern float16 __attribute__((overloadable)) atan2(float16, float16);
extern float __attribute__((overloadable)) ceil(float);
-extern float2 __attribute__((overloadable)) ceil(float2);
-extern float3 __attribute__((overloadable)) ceil(float3);
-extern float4 __attribute__((overloadable)) ceil(float4);
-extern float8 __attribute__((overloadable)) ceil(float8);
-extern float16 __attribute__((overloadable)) ceil(float16);
+//extern float2 __attribute__((overloadable)) ceil(float2);
+//extern float3 __attribute__((overloadable)) ceil(float3);
+//extern float4 __attribute__((overloadable)) ceil(float4);
+//extern float8 __attribute__((overloadable)) ceil(float8);
+//extern float16 __attribute__((overloadable)) ceil(float16);
extern float __attribute__((overloadable)) clamp(float, float, float);
-extern float2 __attribute__((overloadable)) clamp(float2, float2, float2);
-extern float3 __attribute__((overloadable)) clamp(float3, float3, float3);
-extern float4 __attribute__((overloadable)) clamp(float4, float4, float4);
-extern float8 __attribute__((overloadable)) clamp(float8, float8, float8);
-extern float16 __attribute__((overloadable)) clamp(float16, float16, float16);
-extern float __attribute__((overloadable)) clamp(float, float, float);
-extern float2 __attribute__((overloadable)) clamp(float2, float, float);
-extern float3 __attribute__((overloadable)) clamp(float3, float, float);
-extern float4 __attribute__((overloadable)) clamp(float4, float, float);
-extern float8 __attribute__((overloadable)) clamp(float8, float, float);
-extern float16 __attribute__((overloadable)) clamp(float16, float, float);
+//extern float2 __attribute__((overloadable)) clamp(float2, float2, float2);
+//extern float3 __attribute__((overloadable)) clamp(float3, float3, float3);
+//extern float4 __attribute__((overloadable)) clamp(float4, float4, float4);
+//extern float8 __attribute__((overloadable)) clamp(float8, float8, float8);
+//extern float16 __attribute__((overloadable)) clamp(float16, float16, float16);
+//extern float2 __attribute__((overloadable)) clamp(float2, float, float);
+//extern float3 __attribute__((overloadable)) clamp(float3, float, float);
+//extern float4 __attribute__((overloadable)) clamp(float4, float, float);
+//extern float8 __attribute__((overloadable)) clamp(float8, float, float);
+//extern float16 __attribute__((overloadable)) clamp(float16, float, float);
extern float __attribute__((overloadable)) copysign(float, float);
-extern float2 __attribute__((overloadable)) copysign(float2, float2);
-extern float3 __attribute__((overloadable)) copysign(float3, float3);
-extern float4 __attribute__((overloadable)) copysign(float4, float4);
-extern float8 __attribute__((overloadable)) copysign(float8, float8);
-extern float16 __attribute__((overloadable)) copysign(float16, float16);
+//extern float2 __attribute__((overloadable)) copysign(float2, float2);
+//extern float3 __attribute__((overloadable)) copysign(float3, float3);
+//extern float4 __attribute__((overloadable)) copysign(float4, float4);
+//extern float8 __attribute__((overloadable)) copysign(float8, float8);
+//extern float16 __attribute__((overloadable)) copysign(float16, float16);
extern float __attribute__((overloadable)) cos(float);
-extern float2 __attribute__((overloadable)) cos(float2);
-extern float3 __attribute__((overloadable)) cos(float3);
-extern float4 __attribute__((overloadable)) cos(float4);
-extern float8 __attribute__((overloadable)) cos(float8);
-extern float16 __attribute__((overloadable)) cos(float16);
+//extern float2 __attribute__((overloadable)) cos(float2);
+//extern float3 __attribute__((overloadable)) cos(float3);
+//extern float4 __attribute__((overloadable)) cos(float4);
+//extern float8 __attribute__((overloadable)) cos(float8);
+//extern float16 __attribute__((overloadable)) cos(float16);
extern float __attribute__((overloadable)) degrees(float);
-extern float2 __attribute__((overloadable)) degrees(float2);
-extern float3 __attribute__((overloadable)) degrees(float3);
-extern float4 __attribute__((overloadable)) degrees(float4);
-extern float8 __attribute__((overloadable)) degrees(float8);
-extern float16 __attribute__((overloadable)) degrees(float16);
+//extern float2 __attribute__((overloadable)) degrees(float2);
+//extern float3 __attribute__((overloadable)) degrees(float3);
+//extern float4 __attribute__((overloadable)) degrees(float4);
+//extern float8 __attribute__((overloadable)) degrees(float8);
+//extern float16 __attribute__((overloadable)) degrees(float16);
extern float __attribute__((overloadable)) exp(float);
-extern float2 __attribute__((overloadable)) exp(float2);
-extern float3 __attribute__((overloadable)) exp(float3);
-extern float4 __attribute__((overloadable)) exp(float4);
-extern float8 __attribute__((overloadable)) exp(float8);
-extern float16 __attribute__((overloadable)) exp(float16);
+//extern float2 __attribute__((overloadable)) exp(float2);
+//extern float3 __attribute__((overloadable)) exp(float3);
+//extern float4 __attribute__((overloadable)) exp(float4);
+//extern float8 __attribute__((overloadable)) exp(float8);
+//extern float16 __attribute__((overloadable)) exp(float16);
extern float __attribute__((overloadable)) exp2(float);
-extern float2 __attribute__((overloadable)) exp2(float2);
-extern float3 __attribute__((overloadable)) exp2(float3);
-extern float4 __attribute__((overloadable)) exp2(float4);
-extern float8 __attribute__((overloadable)) exp2(float8);
-extern float16 __attribute__((overloadable)) exp2(float16);
+//extern float2 __attribute__((overloadable)) exp2(float2);
+//extern float3 __attribute__((overloadable)) exp2(float3);
+//extern float4 __attribute__((overloadable)) exp2(float4);
+//extern float8 __attribute__((overloadable)) exp2(float8);
+//extern float16 __attribute__((overloadable)) exp2(float16);
extern float __attribute__((overloadable)) exp10(float);
-extern float2 __attribute__((overloadable)) exp10(float2);
-extern float3 __attribute__((overloadable)) exp10(float3);
-extern float4 __attribute__((overloadable)) exp10(float4);
-extern float8 __attribute__((overloadable)) exp10(float8);
-extern float16 __attribute__((overloadable)) exp10(float16);
+//extern float2 __attribute__((overloadable)) exp10(float2);
+//extern float3 __attribute__((overloadable)) exp10(float3);
+//extern float4 __attribute__((overloadable)) exp10(float4);
+//extern float8 __attribute__((overloadable)) exp10(float8);
+//extern float16 __attribute__((overloadable)) exp10(float16);
extern float __attribute__((overloadable)) fabs(float);
-extern float2 __attribute__((overloadable)) fabs(float2);
-extern float3 __attribute__((overloadable)) fabs(float3);
-extern float4 __attribute__((overloadable)) fabs(float4);
-extern float8 __attribute__((overloadable)) fabs(float8);
-extern float16 __attribute__((overloadable)) fabs(float16);
+//extern float2 __attribute__((overloadable)) fabs(float2);
+//extern float3 __attribute__((overloadable)) fabs(float3);
+//extern float4 __attribute__((overloadable)) fabs(float4);
+//extern float8 __attribute__((overloadable)) fabs(float8);
+//extern float16 __attribute__((overloadable)) fabs(float16);
extern float __attribute__((overloadable)) floor(float);
-extern float2 __attribute__((overloadable)) floor(float2);
-extern float3 __attribute__((overloadable)) floor(float3);
-extern float4 __attribute__((overloadable)) floor(float4);
-extern float8 __attribute__((overloadable)) floor(float8);
-extern float16 __attribute__((overloadable)) floor(float16);
+//extern float2 __attribute__((overloadable)) floor(float2);
+//extern float3 __attribute__((overloadable)) floor(float3);
+//extern float4 __attribute__((overloadable)) floor(float4);
+//extern float8 __attribute__((overloadable)) floor(float8);
+//extern float16 __attribute__((overloadable)) floor(float16);
extern float __attribute__((overloadable)) fmax(float, float);
-extern float2 __attribute__((overloadable)) fmax(float2, float2);
-extern float3 __attribute__((overloadable)) fmax(float3, float3);
-extern float4 __attribute__((overloadable)) fmax(float4, float4);
-extern float8 __attribute__((overloadable)) fmax(float8, float8);
-extern float16 __attribute__((overloadable)) fmax(float16, float16);
-extern float2 __attribute__((overloadable)) fmax(float2, float);
-extern float3 __attribute__((overloadable)) fmax(float3, float);
-extern float4 __attribute__((overloadable)) fmax(float4, float);
-extern float8 __attribute__((overloadable)) fmax(float8, float);
-extern float16 __attribute__((overloadable)) fmax(float16, float);
+//extern float2 __attribute__((overloadable)) fmax(float2, float2);
+//extern float3 __attribute__((overloadable)) fmax(float3, float3);
+//extern float4 __attribute__((overloadable)) fmax(float4, float4);
+//extern float8 __attribute__((overloadable)) fmax(float8, float8);
+//extern float16 __attribute__((overloadable)) fmax(float16, float16);
+//extern float2 __attribute__((overloadable)) fmax(float2, float);
+//extern float3 __attribute__((overloadable)) fmax(float3, float);
+//extern float4 __attribute__((overloadable)) fmax(float4, float);
+//extern float8 __attribute__((overloadable)) fmax(float8, float);
+//extern float16 __attribute__((overloadable)) fmax(float16, float);
extern float __attribute__((overloadable)) fmin(float, float);
-extern float2 __attribute__((overloadable)) fmin(float2, float2);
-extern float3 __attribute__((overloadable)) fmin(float3, float3);
-extern float4 __attribute__((overloadable)) fmin(float4, float4);
-extern float8 __attribute__((overloadable)) fmin(float8, float8);
-extern float16 __attribute__((overloadable)) fmin(float16, float16);
-extern float2 __attribute__((overloadable)) fmin(float2, float);
-extern float3 __attribute__((overloadable)) fmin(float3, float);
-extern float4 __attribute__((overloadable)) fmin(float4, float);
-extern float8 __attribute__((overloadable)) fmin(float8, float);
-extern float16 __attribute__((overloadable)) fmin(float16, float);
+//extern float2 __attribute__((overloadable)) fmin(float2, float2);
+//extern float3 __attribute__((overloadable)) fmin(float3, float3);
+//extern float4 __attribute__((overloadable)) fmin(float4, float4);
+//extern float8 __attribute__((overloadable)) fmin(float8, float8);
+//extern float16 __attribute__((overloadable)) fmin(float16, float16);
+//extern float2 __attribute__((overloadable)) fmin(float2, float);
+//extern float3 __attribute__((overloadable)) fmin(float3, float);
+//extern float4 __attribute__((overloadable)) fmin(float4, float);
+//extern float8 __attribute__((overloadable)) fmin(float8, float);
+//extern float16 __attribute__((overloadable)) fmin(float16, float);
extern float __attribute__((overloadable)) fmod(float, float);
-extern float2 __attribute__((overloadable)) fmod(float2, float2);
-extern float3 __attribute__((overloadable)) fmod(float3, float3);
-extern float4 __attribute__((overloadable)) fmod(float4, float4);
-extern float8 __attribute__((overloadable)) fmod(float8, float8);
-extern float16 __attribute__((overloadable)) fmod(float16, float16);
+//extern float2 __attribute__((overloadable)) fmod(float2, float2);
+//extern float3 __attribute__((overloadable)) fmod(float3, float3);
+//extern float4 __attribute__((overloadable)) fmod(float4, float4);
+//extern float8 __attribute__((overloadable)) fmod(float8, float8);
+//extern float16 __attribute__((overloadable)) fmod(float16, float16);
extern float __attribute__((overloadable)) log(float);
-extern float2 __attribute__((overloadable)) log(float2);
-extern float3 __attribute__((overloadable)) log(float3);
-extern float4 __attribute__((overloadable)) log(float4);
-extern float8 __attribute__((overloadable)) log(float8);
-extern float16 __attribute__((overloadable)) log(float16);
+//extern float2 __attribute__((overloadable)) log(float2);
+//extern float3 __attribute__((overloadable)) log(float3);
+//extern float4 __attribute__((overloadable)) log(float4);
+//extern float8 __attribute__((overloadable)) log(float8);
+//extern float16 __attribute__((overloadable)) log(float16);
extern float __attribute__((overloadable)) log2(float);
-extern float2 __attribute__((overloadable)) log2(float2);
-extern float3 __attribute__((overloadable)) log2(float3);
-extern float4 __attribute__((overloadable)) log2(float4);
-extern float8 __attribute__((overloadable)) log2(float8);
-extern float16 __attribute__((overloadable)) log2(float16);
+//extern float2 __attribute__((overloadable)) log2(float2);
+//extern float3 __attribute__((overloadable)) log2(float3);
+//extern float4 __attribute__((overloadable)) log2(float4);
+//extern float8 __attribute__((overloadable)) log2(float8);
+//extern float16 __attribute__((overloadable)) log2(float16);
extern float __attribute__((overloadable)) log10(float);
-extern float2 __attribute__((overloadable)) log10(float2);
-extern float3 __attribute__((overloadable)) log10(float3);
-extern float4 __attribute__((overloadable)) log10(float4);
-extern float8 __attribute__((overloadable)) log10(float8);
-extern float16 __attribute__((overloadable)) log10(float16);
+//extern float2 __attribute__((overloadable)) log10(float2);
+//extern float3 __attribute__((overloadable)) log10(float3);
+//extern float4 __attribute__((overloadable)) log10(float4);
+//extern float8 __attribute__((overloadable)) log10(float8);
+//extern float16 __attribute__((overloadable)) log10(float16);
extern float __attribute__((overloadable)) max(float, float);
-extern float2 __attribute__((overloadable)) max(float2, float2);
-extern float3 __attribute__((overloadable)) max(float3, float3);
-extern float4 __attribute__((overloadable)) max(float4, float4);
-extern float8 __attribute__((overloadable)) max(float8, float8);
-extern float16 __attribute__((overloadable)) max(float16, float16);
+//extern float2 __attribute__((overloadable)) max(float2, float2);
+//extern float3 __attribute__((overloadable)) max(float3, float3);
+//extern float4 __attribute__((overloadable)) max(float4, float4);
+//extern float8 __attribute__((overloadable)) max(float8, float8);
+//extern float16 __attribute__((overloadable)) max(float16, float16);
extern float __attribute__((overloadable)) min(float, float);
-extern float2 __attribute__((overloadable)) min(float2, float2);
-extern float3 __attribute__((overloadable)) min(float3, float3);
-extern float4 __attribute__((overloadable)) min(float4, float4);
-extern float8 __attribute__((overloadable)) min(float8, float8);
-extern float16 __attribute__((overloadable)) min(float16, float16);
+//extern float2 __attribute__((overloadable)) min(float2, float2);
+//extern float3 __attribute__((overloadable)) min(float3, float3);
+//extern float4 __attribute__((overloadable)) min(float4, float4);
+//extern float8 __attribute__((overloadable)) min(float8, float8);
+//extern float16 __attribute__((overloadable)) min(float16, float16);
extern float __attribute__((overloadable)) mix(float, float, float);
-extern float2 __attribute__((overloadable)) mix(float2, float2, float2);
-extern float3 __attribute__((overloadable)) mix(float3, float3, float3);
-extern float4 __attribute__((overloadable)) mix(float4, float4, float4);
-extern float8 __attribute__((overloadable)) mix(float8, float8, float8);
-extern float16 __attribute__((overloadable)) mix(float16, float16, float16);
-extern float __attribute__((overloadable)) mix(float, float, float);
-extern float2 __attribute__((overloadable)) mix(float2, float2, float);
-extern float3 __attribute__((overloadable)) mix(float3, float3, float);
-extern float4 __attribute__((overloadable)) mix(float4, float4, float);
-extern float8 __attribute__((overloadable)) mix(float8, float8, float);
-extern float16 __attribute__((overloadable)) mix(float16, float16, float);
+//extern float2 __attribute__((overloadable)) mix(float2, float2, float2);
+//extern float3 __attribute__((overloadable)) mix(float3, float3, float3);
+//extern float4 __attribute__((overloadable)) mix(float4, float4, float4);
+//extern float8 __attribute__((overloadable)) mix(float8, float8, float8);
+//extern float16 __attribute__((overloadable)) mix(float16, float16, float16);
+//extern float2 __attribute__((overloadable)) mix(float2, float2, float);
+//extern float3 __attribute__((overloadable)) mix(float3, float3, float);
+//extern float4 __attribute__((overloadable)) mix(float4, float4, float);
+//extern float8 __attribute__((overloadable)) mix(float8, float8, float);
+//extern float16 __attribute__((overloadable)) mix(float16, float16, float);
extern float __attribute__((overloadable)) pow(float, float);
-extern float2 __attribute__((overloadable)) pow(float2, float2);
-extern float3 __attribute__((overloadable)) pow(float3, float3);
-extern float4 __attribute__((overloadable)) pow(float4, float4);
-extern float8 __attribute__((overloadable)) pow(float8, float8);
-extern float16 __attribute__((overloadable)) pow(float16, float16);
+//extern float2 __attribute__((overloadable)) pow(float2, float2);
+//extern float3 __attribute__((overloadable)) pow(float3, float3);
+//extern float4 __attribute__((overloadable)) pow(float4, float4);
+//extern float8 __attribute__((overloadable)) pow(float8, float8);
+//extern float16 __attribute__((overloadable)) pow(float16, float16);
extern float __attribute__((overloadable)) radians(float);
-extern float2 __attribute__((overloadable)) radians(float2);
-extern float3 __attribute__((overloadable)) radians(float3);
-extern float4 __attribute__((overloadable)) radians(float4);
-extern float8 __attribute__((overloadable)) radians(float8);
-extern float16 __attribute__((overloadable)) radians(float16);
+//extern float2 __attribute__((overloadable)) radians(float2);
+//extern float3 __attribute__((overloadable)) radians(float3);
+//extern float4 __attribute__((overloadable)) radians(float4);
+//extern float8 __attribute__((overloadable)) radians(float8);
+//extern float16 __attribute__((overloadable)) radians(float16);
extern float __attribute__((overloadable)) rint(float);
-extern float2 __attribute__((overloadable)) rint(float2);
-extern float3 __attribute__((overloadable)) rint(float3);
-extern float4 __attribute__((overloadable)) rint(float4);
-extern float8 __attribute__((overloadable)) rint(float8);
-extern float16 __attribute__((overloadable)) rint(float16);
+//extern float2 __attribute__((overloadable)) rint(float2);
+//extern float3 __attribute__((overloadable)) rint(float3);
+//extern float4 __attribute__((overloadable)) rint(float4);
+//extern float8 __attribute__((overloadable)) rint(float8);
+//extern float16 __attribute__((overloadable)) rint(float16);
extern float __attribute__((overloadable)) round(float);
-extern float2 __attribute__((overloadable)) round(float2);
-extern float3 __attribute__((overloadable)) round(float3);
-extern float4 __attribute__((overloadable)) round(float4);
-extern float8 __attribute__((overloadable)) round(float8);
-extern float16 __attribute__((overloadable)) round(float16);
+//extern float2 __attribute__((overloadable)) round(float2);
+//extern float3 __attribute__((overloadable)) round(float3);
+//extern float4 __attribute__((overloadable)) round(float4);
+//extern float8 __attribute__((overloadable)) round(float8);
+//extern float16 __attribute__((overloadable)) round(float16);
extern float __attribute__((overloadable)) rsqrt(float);
-extern float2 __attribute__((overloadable)) rsqrt(float2);
-extern float3 __attribute__((overloadable)) rsqrt(float3);
-extern float4 __attribute__((overloadable)) rsqrt(float4);
-extern float8 __attribute__((overloadable)) rsqrt(float8);
-extern float16 __attribute__((overloadable)) rsqrt(float16);
+//extern float2 __attribute__((overloadable)) rsqrt(float2);
+//extern float3 __attribute__((overloadable)) rsqrt(float3);
+//extern float4 __attribute__((overloadable)) rsqrt(float4);
+//extern float8 __attribute__((overloadable)) rsqrt(float8);
+//extern float16 __attribute__((overloadable)) rsqrt(float16);
extern float __attribute__((overloadable)) sign(float);
-extern float2 __attribute__((overloadable)) sign(float2);
-extern float3 __attribute__((overloadable)) sign(float3);
-extern float4 __attribute__((overloadable)) sign(float4);
-extern float8 __attribute__((overloadable)) sign(float8);
-extern float16 __attribute__((overloadable)) sign(float16);
+//extern float2 __attribute__((overloadable)) sign(float2);
+//extern float3 __attribute__((overloadable)) sign(float3);
+//extern float4 __attribute__((overloadable)) sign(float4);
+//extern float8 __attribute__((overloadable)) sign(float8);
+//extern float16 __attribute__((overloadable)) sign(float16);
extern float __attribute__((overloadable)) sin(float);
-extern float2 __attribute__((overloadable)) sin(float2);
-extern float3 __attribute__((overloadable)) sin(float3);
-extern float4 __attribute__((overloadable)) sin(float4);
-extern float8 __attribute__((overloadable)) sin(float8);
-extern float16 __attribute__((overloadable)) sin(float16);
+//extern float2 __attribute__((overloadable)) sin(float2);
+//extern float3 __attribute__((overloadable)) sin(float3);
+//extern float4 __attribute__((overloadable)) sin(float4);
+//extern float8 __attribute__((overloadable)) sin(float8);
+//extern float16 __attribute__((overloadable)) sin(float16);
extern float __attribute__((overloadable)) sqrt(float);
-extern float2 __attribute__((overloadable)) sqrt(float2);
-extern float3 __attribute__((overloadable)) sqrt(float3);
-extern float4 __attribute__((overloadable)) sqrt(float4);
-extern float8 __attribute__((overloadable)) sqrt(float8);
-extern float16 __attribute__((overloadable)) sqrt(float16);
+//extern float2 __attribute__((overloadable)) sqrt(float2);
+//extern float3 __attribute__((overloadable)) sqrt(float3);
+//extern float4 __attribute__((overloadable)) sqrt(float4);
+//extern float8 __attribute__((overloadable)) sqrt(float8);
+//extern float16 __attribute__((overloadable)) sqrt(float16);
extern float __attribute__((overloadable)) tan(float);
-extern float2 __attribute__((overloadable)) tan(float2);
-extern float3 __attribute__((overloadable)) tan(float3);
-extern float4 __attribute__((overloadable)) tan(float4);
-extern float8 __attribute__((overloadable)) tan(float8);
-extern float16 __attribute__((overloadable)) tan(float16);
+//extern float2 __attribute__((overloadable)) tan(float2);
+//extern float3 __attribute__((overloadable)) tan(float3);
+//extern float4 __attribute__((overloadable)) tan(float4);
+//extern float8 __attribute__((overloadable)) tan(float8);
+//extern float16 __attribute__((overloadable)) tan(float16);
extern float __attribute__((overloadable)) trunc(float);
-extern float2 __attribute__((overloadable)) trunc(float2);
-extern float3 __attribute__((overloadable)) trunc(float3);
-extern float4 __attribute__((overloadable)) trunc(float4);
-extern float8 __attribute__((overloadable)) trunc(float8);
-extern float16 __attribute__((overloadable)) trunc(float16);
+//extern float2 __attribute__((overloadable)) trunc(float2);
+//extern float3 __attribute__((overloadable)) trunc(float3);
+//extern float4 __attribute__((overloadable)) trunc(float4);
+//extern float8 __attribute__((overloadable)) trunc(float8);
+//extern float16 __attribute__((overloadable)) trunc(float16);
@@ -268,11 +266,11 @@
// Int ops
extern int __attribute__((overloadable)) abs(int);
-extern int2 __attribute__((overloadable)) abs(int2);
-extern int3 __attribute__((overloadable)) abs(int3);
-extern int4 __attribute__((overloadable)) abs(int4);
-extern int8 __attribute__((overloadable)) abs(int8);
-extern int16 __attribute__((overloadable)) abs(int16);
+//extern int2 __attribute__((overloadable)) abs(int2);
+//extern int3 __attribute__((overloadable)) abs(int3);
+//extern int4 __attribute__((overloadable)) abs(int4);
+//extern int8 __attribute__((overloadable)) abs(int8);
+//extern int16 __attribute__((overloadable)) abs(int16);
diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh
index 4198a74..b710146 100644
--- a/libs/rs/scriptc/rs_types.rsh
+++ b/libs/rs/scriptc/rs_types.rsh
@@ -68,4 +68,29 @@
typedef int int16 __attribute__((ext_vector_type(16)));
+// RS_KIND_POSITION
+typedef float rs_position1;
+typedef float2 rs_position2;
+typedef float3 rs_position3;
+typedef float4 rs_position4;
+
+// RS_KIND_COLOR
+typedef float3 rs_color3f;
+typedef float4 rs_color4f;
+typedef uchar4 rs_color4u;
+
+// RS_KIND_NORMAL
+typedef float3 rs_normal;
+
+// RS_KIND_POINT_SIZE
+typedef float rs_point_size;
+
+// RS_KIND_TEXTURE
+typedef float rs_texture_coord1;
+typedef float2 rs_texture_coord2;
+typedef float3 rs_texture_coord3;
+typedef float4 rs_texture_coord4;
+
+// RS_KIND_INDEX
+typedef ushort rs_index;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 47e2da2..2e3eae0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1523,4 +1523,22 @@
* {@hide}
*/
private IBinder mICallBack = new Binder();
+
+ /**
+ * Checks whether the phone is in silent mode, with or without vibrate.
+ *
+ * @return true if phone is in silent mode, with or without vibrate.
+ *
+ * @see #getRingerMode()
+ *
+ * @hide pending API Council approval
+ */
+ public boolean isSilentMode() {
+ int ringerMode = getRingerMode();
+ boolean silentMode =
+ (ringerMode == RINGER_MODE_SILENT) ||
+ (ringerMode == RINGER_MODE_VIBRATE);
+ return silentMode;
+ }
+
}
diff --git a/opengl/tests/testViewport/Android.mk b/opengl/tests/testViewport/Android.mk
new file mode 100644
index 0000000..3da59b5
--- /dev/null
+++ b/opengl/tests/testViewport/Android.mk
@@ -0,0 +1,22 @@
+#########################################################################
+# OpenGL ES JNI sample
+# This makefile builds both an activity and a shared library.
+#########################################################################
+ifneq ($(TARGET_SIMULATOR),true) # not 64 bit clean
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TestViewport
+
+include $(BUILD_PACKAGE)
+
+endif # TARGET_SIMULATOR
diff --git a/opengl/tests/testViewport/AndroidManifest.xml b/opengl/tests/testViewport/AndroidManifest.xml
new file mode 100644
index 0000000..f4a493e
--- /dev/null
+++ b/opengl/tests/testViewport/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, 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.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test">
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <application
+ android:label="@string/test_activity">
+ <activity android:name="TestActivity"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+ android:configChanges="orientation|keyboardHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/opengl/tests/testViewport/README b/opengl/tests/testViewport/README
new file mode 100644
index 0000000..c06abc9
--- /dev/null
+++ b/opengl/tests/testViewport/README
@@ -0,0 +1,28 @@
+Repro steps:
+
+build, install and run the attached test program TestViewport.apk
+
+Run on Sapphire with Froyo.
+
+The program clears the screen to blue, then draws a full screen white quad that
+is alligned to the screen.
+(Therefore the whole screen should appear to be white.)
+
+
+Note that screen is all white.
+
+Rotate screen 90 degrees.
+
+Expected: screen is still all white.
+
+Actual: screen is blue with offset white rectangle.
+
+This bug only happens on Sapphire, it works correctly on Passion.
+
+What happens:
+
+I think the bug is that the gl.glViewport() call in onSurfaceChanged() is
+being ignored by the OpenGL driver.
+
+NOTE: If a gl.glViewport call is added at the beginning of the onDrawFrame()
+call (which means it is called before every draw), the program runs correctly.
diff --git a/opengl/tests/testViewport/res/values/strings.xml b/opengl/tests/testViewport/res/values/strings.xml
new file mode 100644
index 0000000..f4b8bbb
--- /dev/null
+++ b/opengl/tests/testViewport/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** 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.
+*/
+-->
+
+<!-- This file contains resource definitions for displayed strings, allowing
+ them to be changed based on the locale and options. -->
+
+<resources>
+ <!-- Simple strings. -->
+ <string name="test_activity">Test Viewport</string>
+
+</resources>
+
diff --git a/opengl/tests/testViewport/src/com/android/test/TestActivity.java b/opengl/tests/testViewport/src/com/android/test/TestActivity.java
new file mode 100644
index 0000000..cc7e450
--- /dev/null
+++ b/opengl/tests/testViewport/src/com/android/test/TestActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007 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.test;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestActivity extends Activity {
+ private final static String TAG = "TestActivity";
+ TestView mView;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mView = new TestView(getApplication());
+ mView.setFocusableInTouchMode(true);
+ setContentView(mView);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mView.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mView.onResume();
+ }
+}
diff --git a/opengl/tests/testViewport/src/com/android/test/TestView.java b/opengl/tests/testViewport/src/com/android/test/TestView.java
new file mode 100644
index 0000000..23cc37d
--- /dev/null
+++ b/opengl/tests/testViewport/src/com/android/test/TestView.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2009 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.test;
+/*
+ * Copyright (C) 2008 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.
+ */
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.FloatBuffer;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying an OpenGL animation. This allows the animation to run in a
+ * separate thread, without requiring that it be driven by the update mechanism
+ * of the view hierarchy.
+ *
+ * The application-specific rendering code is delegated to a GLView.Renderer
+ * instance.
+ */
+class TestView extends GLSurfaceView {
+ TestView(Context context) {
+ super(context);
+ init();
+ }
+
+ public TestView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ setRenderer(new Renderer());
+ setRenderMode(RENDERMODE_WHEN_DIRTY);
+ }
+
+ /** A grid is a topologically rectangular array of vertices.
+ *
+ * The vertex and index data are held in VBO objects because on most
+ * GPUs VBO objects are the fastest way of rendering static vertex
+ * and index data.
+ *
+ */
+
+ private static class Grid {
+ // Size of vertex data elements in bytes:
+ final static int FLOAT_SIZE = 4;
+ final static int CHAR_SIZE = 2;
+
+ // Vertex structure:
+ // float x, y, z;
+
+ final static int VERTEX_SIZE = 3 * FLOAT_SIZE;
+
+ private int mVertexBufferObjectId;
+ private int mElementBufferObjectId;
+
+ // These buffers are used to hold the vertex and index data while
+ // constructing the grid. Once createBufferObjects() is called
+ // the buffers are nulled out to save memory.
+
+ private ByteBuffer mVertexByteBuffer;
+ private FloatBuffer mVertexBuffer;
+ private CharBuffer mIndexBuffer;
+
+ private int mW;
+ private int mH;
+ private int mIndexCount;
+
+ public Grid(int w, int h) {
+ if (w < 0 || w >= 65536) {
+ throw new IllegalArgumentException("w");
+ }
+ if (h < 0 || h >= 65536) {
+ throw new IllegalArgumentException("h");
+ }
+ if (w * h >= 65536) {
+ throw new IllegalArgumentException("w * h >= 65536");
+ }
+
+ mW = w;
+ mH = h;
+ int size = w * h;
+
+ mVertexByteBuffer = ByteBuffer.allocateDirect(VERTEX_SIZE * size)
+ .order(ByteOrder.nativeOrder());
+ mVertexBuffer = mVertexByteBuffer.asFloatBuffer();
+
+ int quadW = mW - 1;
+ int quadH = mH - 1;
+ int quadCount = quadW * quadH;
+ int indexCount = quadCount * 6;
+ mIndexCount = indexCount;
+ mIndexBuffer = ByteBuffer.allocateDirect(CHAR_SIZE * indexCount)
+ .order(ByteOrder.nativeOrder()).asCharBuffer();
+
+ /*
+ * Initialize triangle list mesh.
+ *
+ * [0]-----[ 1] ...
+ * | / |
+ * | / |
+ * | / |
+ * [w]-----[w+1] ...
+ * | |
+ *
+ */
+
+ {
+ int i = 0;
+ for (int y = 0; y < quadH; y++) {
+ for (int x = 0; x < quadW; x++) {
+ char a = (char) (y * mW + x);
+ char b = (char) (y * mW + x + 1);
+ char c = (char) ((y + 1) * mW + x);
+ char d = (char) ((y + 1) * mW + x + 1);
+
+ mIndexBuffer.put(i++, a);
+ mIndexBuffer.put(i++, c);
+ mIndexBuffer.put(i++, b);
+
+ mIndexBuffer.put(i++, b);
+ mIndexBuffer.put(i++, c);
+ mIndexBuffer.put(i++, d);
+ }
+ }
+ }
+
+ }
+
+ public void set(int i, int j, float x, float y, float z) {
+ if (i < 0 || i >= mW) {
+ throw new IllegalArgumentException("i");
+ }
+ if (j < 0 || j >= mH) {
+ throw new IllegalArgumentException("j");
+ }
+
+ int index = mW * j + i;
+
+ mVertexBuffer.position(index * VERTEX_SIZE / FLOAT_SIZE);
+ mVertexBuffer.put(x);
+ mVertexBuffer.put(y);
+ mVertexBuffer.put(z);
+ }
+
+ public void createBufferObjects(GL gl) {
+ // Generate a the vertex and element buffer IDs
+ int[] vboIds = new int[2];
+ GL11 gl11 = (GL11) gl;
+ gl11.glGenBuffers(2, vboIds, 0);
+ mVertexBufferObjectId = vboIds[0];
+ mElementBufferObjectId = vboIds[1];
+
+ // Upload the vertex data
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
+ mVertexByteBuffer.position(0);
+ gl11.glBufferData(GL11.GL_ARRAY_BUFFER, mVertexByteBuffer.capacity(), mVertexByteBuffer, GL11.GL_STATIC_DRAW);
+
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
+ mIndexBuffer.position(0);
+ gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer.capacity() * CHAR_SIZE, mIndexBuffer, GL11.GL_STATIC_DRAW);
+
+ // We don't need the in-memory data any more
+ mVertexBuffer = null;
+ mVertexByteBuffer = null;
+ mIndexBuffer = null;
+ }
+
+ public void draw(GL10 gl) {
+ GL11 gl11 = (GL11) gl;
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
+ gl11.glVertexPointer(3, GL10.GL_FLOAT, VERTEX_SIZE, 0);
+
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
+ gl11.glDrawElements(GL10.GL_TRIANGLES, mIndexCount, GL10.GL_UNSIGNED_SHORT, 0);
+ gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
+ gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
+ gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+ }
+
+
+ private class Renderer implements GLSurfaceView.Renderer {
+ private static final String TAG = "Renderer";
+ private Grid mGrid;
+
+ public void onDrawFrame(GL10 gl) {
+ gl.glClearColor(0,0,1,1);
+ gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
+ mGrid.draw(gl);
+ }
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ gl.glViewport(0, 0, width, height);
+ gl.glMatrixMode(GL11.GL_PROJECTION);
+ gl.glLoadIdentity();
+ gl.glOrthof(0, width, height, 0, -1, 1);
+ gl.glMatrixMode(GL11.GL_MODELVIEW);
+ createGrid(gl, width, height);
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ }
+
+ private void createGrid(GL10 gl, float w, float h) {
+ mGrid = new Grid(2, 2);
+ for (int j = 0; j < 2; j++) {
+ for (int i = 0; i < 2; i++) {
+ float x = w * i;
+ float y = h * j;
+ float z = 0.0f;
+ mGrid.set(i,j, x, y, z);
+ }
+ }
+ mGrid.createBufferObjects(gl);
+ }
+ }
+}
+
diff --git a/policy/com/android/internal/policy/impl/LockScreen.java b/policy/com/android/internal/policy/impl/LockScreen.java
index baed9eb..381b913 100644
--- a/policy/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/com/android/internal/policy/impl/LockScreen.java
@@ -66,7 +66,7 @@
private TextView mStatus1;
private TextView mStatus2;
private TextView mScreenLocked;
- private Button mEmergencyCallButton;
+ private TextView mEmergencyCallText;
// current configuration state of keyboard and display
private int mKeyboardHidden;
@@ -203,18 +203,12 @@
mStatus1 = (TextView) findViewById(R.id.status1);
mStatus2 = (TextView) findViewById(R.id.status2);
- mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
- mEmergencyCallButton.setText(R.string.lockscreen_emergency_call);
- mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
mScreenLocked = (TextView) findViewById(R.id.screenLocked);
mSelector = (SlidingTab) findViewById(R.id.tab_selector);
mSelector.setHoldAfterTrigger(true, false);
mSelector.setLeftHintText(R.string.lockscreen_unlock_label);
- mEmergencyCallButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- mCallback.takeEmergencyCallAction();
- }
- });
+
+ mEmergencyCallText = (TextView) findViewById(R.id.emergencyCallText);
setFocusable(true);
setFocusableInTouchMode(true);
@@ -269,7 +263,6 @@
refreshBatteryStringAndIcon();
refreshAlarmDisplay();
- mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
mTimeFormat = DateFormat.getTimeFormat(getContext());
mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year);
@@ -340,8 +333,6 @@
* @param iconResourceId The left hand icon.
*/
private void toastMessage(final TextView textView, final String text, final int color, final int iconResourceId) {
- if (DBG) android.util.Log.d("LockScreen", "toastMessage(text=" + text +", color=" + color + ")");
-
if (mPendingR1 != null) {
textView.removeCallbacks(mPendingR1);
mPendingR1 = null;
@@ -499,9 +490,8 @@
* Update the layout to match the current status.
*/
private void updateLayout(Status status) {
- // The emergency call button appears where the carrier would
- // ordinarily be shown, so if one is VISIBLE the other must be
- // INVISIBLE.
+ // The emergency call button no longer appears on this screen.
+ if (DBG) Log.d(TAG, "updateLayout: status=" + status);
switch (status) {
case Normal:
// text
@@ -509,12 +499,13 @@
getCarrierString(
mUpdateMonitor.getTelephonyPlmn(),
mUpdateMonitor.getTelephonySpn()));
-// mScreenLocked.setText(R.string.lockscreen_screen_locked);
+ // unnecessary clutter
+ //mScreenLocked.setText(R.string.lockscreen_screen_locked);
// layout
- mScreenLocked.setVisibility(View.VISIBLE);
+ mScreenLocked.setVisibility(View.INVISIBLE);
mSelector.setVisibility(View.VISIBLE);
- mEmergencyCallButton.setVisibility(View.GONE);
+ mEmergencyCallText.setVisibility(View.GONE);
break;
case NetworkLocked:
// The carrier string shows both sim card status (i.e. No Sim Card) and
@@ -528,35 +519,30 @@
// layout
mScreenLocked.setVisibility(View.VISIBLE);
mSelector.setVisibility(View.VISIBLE);
- mEmergencyCallButton.setVisibility(View.GONE);
+ mEmergencyCallText.setVisibility(View.GONE);
break;
case SimMissing:
// text
- mCarrier.setText("");
- mScreenLocked.setText(
- getCarrierString(
- mUpdateMonitor.getTelephonyPlmn(),
- getContext().getText(R.string.lockscreen_missing_sim_message_short)));
- // previously shown here: lockscreen_instructions_when_pattern_disabled
+ mCarrier.setText(R.string.lockscreen_missing_sim_message_short);
+ mScreenLocked.setText(R.string.lockscreen_missing_sim_instructions);
// layout
mScreenLocked.setVisibility(View.VISIBLE);
mSelector.setVisibility(View.VISIBLE);
- mEmergencyCallButton.setVisibility(View.VISIBLE);
+ mEmergencyCallText.setVisibility(View.VISIBLE);
break;
case SimMissingLocked:
// text
- mCarrier.setText("");
- mScreenLocked.setText(
+ mCarrier.setText(
getCarrierString(
mUpdateMonitor.getTelephonyPlmn(),
getContext().getText(R.string.lockscreen_missing_sim_message_short)));
- // previously shown here: lockscreen_missing_sim_instructions
+ mScreenLocked.setText(R.string.lockscreen_missing_sim_instructions);
// layout
mScreenLocked.setVisibility(View.VISIBLE);
mSelector.setVisibility(View.GONE);
- mEmergencyCallButton.setVisibility(View.VISIBLE);
+ mEmergencyCallText.setVisibility(View.VISIBLE);
break;
case SimLocked:
// text
@@ -568,21 +554,20 @@
// layout
mScreenLocked.setVisibility(View.INVISIBLE);
mSelector.setVisibility(View.VISIBLE);
- mEmergencyCallButton.setVisibility(View.GONE);
+ mEmergencyCallText.setVisibility(View.GONE);
break;
case SimPukLocked:
// text
- mCarrier.setText("");
- mScreenLocked.setText(
+ mCarrier.setText(
getCarrierString(
mUpdateMonitor.getTelephonyPlmn(),
getContext().getText(R.string.lockscreen_sim_puk_locked_message)));
- // previously shown here: lockscreen_sim_puk_locked_instructions);
+ mScreenLocked.setText(R.string.lockscreen_sim_puk_locked_instructions);
// layout
mScreenLocked.setVisibility(View.VISIBLE);
mSelector.setVisibility(View.GONE);
- mEmergencyCallButton.setVisibility(View.VISIBLE);
+ mEmergencyCallText.setVisibility(View.VISIBLE);
break;
}
}
@@ -655,7 +640,6 @@
/** {@inheritDoc} */
public void onResume() {
resetStatusInfo(mUpdateMonitor);
- mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
}
/** {@inheritDoc} */
@@ -673,6 +657,5 @@
}
public void onPhoneStateChanged(String newState) {
- mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
}
}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index f9c1a93..b92480f 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -462,10 +462,11 @@
mEnabledProviders.add(passiveProvider.getName());
// initialize external network location and geocoder services
+ PackageManager pm = mContext. getPackageManager();
Resources resources = mContext.getResources();
String serviceName = resources.getString(
com.android.internal.R.string.config_networkLocationProvider);
- if (serviceName != null) {
+ if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
mNetworkLocationProvider =
new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
serviceName, mLocationHandler);
@@ -473,7 +474,7 @@
}
serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider);
- if (serviceName != null) {
+ if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
mGeocodeProvider = new GeocoderProxy(mContext, serviceName);
}
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index 7a74fd4..d6f0d08 100755
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -241,9 +241,9 @@
sAGpsInterface->init(&sAGpsCallbacks);
if (!sGpsNiInterface)
- sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+ sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
if (sGpsNiInterface)
- sGpsNiInterface->init(&sGpsNiCallbacks);
+ sGpsNiInterface->init(&sGpsNiCallbacks);
if (!sGpsDebugInterface)
sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
@@ -494,12 +494,10 @@
static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
jint notifId, jint response)
{
- if (!sGpsNiInterface) {
+ if (!sGpsNiInterface)
sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
- }
- if (sGpsNiInterface) {
+ if (sGpsNiInterface)
sGpsNiInterface->respond(notifId, response);
- }
}
static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
diff --git a/tools/layoutlib/README b/tools/layoutlib/README
new file mode 100644
index 0000000..0fea9bd
--- /dev/null
+++ b/tools/layoutlib/README
@@ -0,0 +1,4 @@
+Layoutlib is a custom version of the android View framework designed to run inside Eclipse.
+The goal of the library is to provide layout rendering in Eclipse that are very very close to their rendering on devices.
+
+None of the com.android.* or android.* classes in layoutlib run on devices.
\ No newline at end of file