summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountManagerService.java172
-rw-r--r--core/java/android/accounts/AuthenticatorBindHelper.java258
-rw-r--r--core/java/android/accounts/GrantCredentialsPermissionActivity.java138
-rw-r--r--core/java/android/app/Activity.java4
-rw-r--r--core/java/android/app/ActivityManager.java64
-rw-r--r--core/java/android/app/ActivityManagerNative.java81
-rw-r--r--core/java/android/app/ActivityThread.java3
-rw-r--r--core/java/android/app/AlertDialog.java2
-rw-r--r--core/java/android/app/AliasActivity.java2
-rw-r--r--core/java/android/app/ApplicationContext.java59
-rw-r--r--core/java/android/app/ApplicationErrorReport.java28
-rw-r--r--core/java/android/app/Dialog.java4
-rw-r--r--core/java/android/app/ExpandableListActivity.java18
-rw-r--r--core/java/android/app/IActivityController.aidl5
-rw-r--r--core/java/android/app/IActivityManager.java18
-rw-r--r--core/java/android/app/ISearchManager.aidl2
-rw-r--r--core/java/android/app/ListActivity.java18
-rw-r--r--core/java/android/app/SearchDialog.java29
-rw-r--r--core/java/android/app/SearchManager.java124
-rw-r--r--core/java/android/app/SearchSourceSelector.java197
-rw-r--r--core/java/android/app/SearchableInfo.aidl (renamed from core/java/android/server/search/SearchableInfo.aidl)2
-rw-r--r--core/java/android/app/SearchableInfo.java (renamed from core/java/android/server/search/SearchableInfo.java)33
-rw-r--r--core/java/android/app/Service.java15
-rw-r--r--core/java/android/app/SuggestionsAdapter.java1
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java4
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java2
-rw-r--r--core/java/android/content/AbstractCursorEntityIterator.java121
-rw-r--r--core/java/android/content/AbstractSyncableContentProvider.java755
-rw-r--r--core/java/android/content/AbstractTableMerger.java599
-rw-r--r--core/java/android/content/AbstractThreadedSyncAdapter.java44
-rw-r--r--core/java/android/content/AsyncQueryHandler.java68
-rw-r--r--core/java/android/content/ContentProvider.java51
-rw-r--r--core/java/android/content/ContentProviderClient.java11
-rw-r--r--core/java/android/content/ContentProviderNative.java98
-rw-r--r--core/java/android/content/ContentResolver.java134
-rw-r--r--core/java/android/content/Context.java28
-rw-r--r--core/java/android/content/CursorEntityIterator.java88
-rw-r--r--core/java/android/content/Entity.aidl20
-rw-r--r--core/java/android/content/Entity.java44
-rw-r--r--core/java/android/content/EntityIterator.java3
-rw-r--r--core/java/android/content/IContentProvider.java10
-rw-r--r--core/java/android/content/IEntityIterator.java210
-rw-r--r--core/java/android/content/ISyncAdapter.aidl8
-rw-r--r--core/java/android/content/Intent.java122
-rw-r--r--core/java/android/content/IntentFilter.java2
-rw-r--r--core/java/android/content/SyncAdapter.java73
-rw-r--r--core/java/android/content/SyncContext.java10
-rw-r--r--core/java/android/content/SyncManager.java85
-rw-r--r--core/java/android/content/SyncStateContentProviderHelper.java243
-rw-r--r--core/java/android/content/SyncStorageEngine.java11
-rw-r--r--core/java/android/content/SyncableContentProvider.java237
-rw-r--r--core/java/android/content/TempProviderSyncAdapter.java585
-rw-r--r--core/java/android/content/TempProviderSyncResult.java36
-rw-r--r--core/java/android/content/package.html14
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java10
-rw-r--r--core/java/android/content/pm/PackageParser.java8
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java2
-rw-r--r--core/java/android/content/res/ColorStateList.java2
-rw-r--r--core/java/android/content/res/Resources.java2
-rw-r--r--core/java/android/content/res/StringBlock.java2
-rw-r--r--core/java/android/content/res/TypedArray.java2
-rw-r--r--core/java/android/content/res/XmlBlock.java2
-rw-r--r--core/java/android/database/DatabaseUtils.java96
-rw-r--r--core/java/android/database/sqlite/SQLiteCompiledSql.java111
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java12
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java214
-rw-r--r--core/java/android/database/sqlite/SQLiteDebug.java6
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java125
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java19
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java38
-rw-r--r--core/java/android/ddm/DdmHandleHello.java4
-rw-r--r--core/java/android/gesture/GestureStore.java7
-rwxr-xr-xcore/java/android/gesture/GestureUtilities.java32
-rwxr-xr-xcore/java/android/gesture/Instance.java2
-rw-r--r--core/java/android/gesture/InstanceLearner.java4
-rwxr-xr-xcore/java/android/gesture/Learner.java2
-rw-r--r--core/java/android/hardware/SensorManager.java6
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java20
-rwxr-xr-xcore/java/android/inputmethodservice/KeyboardView.java15
-rw-r--r--core/java/android/net/MobileDataStateTracker.java23
-rw-r--r--core/java/android/net/NetworkConnectivityListener.java220
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java5
-rw-r--r--core/java/android/net/TrafficStats.java (renamed from core/java/android/os/NetStat.java)74
-rw-r--r--core/java/android/net/Uri.java61
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java508
-rw-r--r--core/java/android/net/http/CertificateChainValidator.java4
-rw-r--r--core/java/android/net/http/Connection.java14
-rw-r--r--core/java/android/net/http/ConnectionThread.java19
-rw-r--r--core/java/android/net/http/DomainNameChecker.java277
-rw-r--r--core/java/android/net/http/HttpConnection.java3
-rw-r--r--core/java/android/net/http/HttpsConnection.java14
-rw-r--r--core/java/android/net/http/RequestHandle.java25
-rw-r--r--core/java/android/net/http/RequestQueue.java71
-rw-r--r--core/java/android/net/http/SslError.java2
-rw-r--r--core/java/android/os/AsyncTask.java2
-rw-r--r--core/java/android/os/Build.java14
-rw-r--r--core/java/android/os/Debug.java10
-rw-r--r--core/java/android/os/DropBoxManager.aidl (renamed from core/java/android/os/HandlerState.java)18
-rw-r--r--core/java/android/os/DropBoxManager.java281
-rw-r--r--core/java/android/os/Environment.java64
-rw-r--r--core/java/android/os/FileObserver.java4
-rw-r--r--core/java/android/os/FileUtils.java8
-rw-r--r--core/java/android/os/HandlerStateMachine.java290
-rw-r--r--core/java/android/os/Hardware.java49
-rw-r--r--core/java/android/os/ICheckinService.aidl18
-rw-r--r--core/java/android/os/IMountService.aidl45
-rw-r--r--core/java/android/os/IPowerManager.aidl5
-rwxr-xr-xcore/java/android/os/IVibratorService.aidl (renamed from core/java/android/os/IHardwareService.aidl)11
-rw-r--r--core/java/android/os/LocalPowerManager.java5
-rw-r--r--core/java/android/os/MemoryFile.java13
-rw-r--r--core/java/android/os/MessageQueue.java4
-rw-r--r--core/java/android/os/PowerManager.java20
-rw-r--r--core/java/android/os/RecoverySystem.java418
-rw-r--r--core/java/android/os/Vibrator.java6
-rw-r--r--core/java/android/pim/vcard/Constants.java94
-rw-r--r--core/java/android/pim/vcard/JapaneseUtils.java380
-rw-r--r--core/java/android/pim/vcard/VCardBuilder.java1915
-rw-r--r--core/java/android/pim/vcard/VCardComposer.java1821
-rw-r--r--core/java/android/pim/vcard/VCardConfig.java367
-rw-r--r--core/java/android/pim/vcard/VCardConstants.java152
-rw-r--r--core/java/android/pim/vcard/VCardEntry.java (renamed from core/java/android/pim/vcard/ContactStruct.java)1116
-rw-r--r--core/java/android/pim/vcard/VCardEntryCommitter.java (renamed from core/java/android/pim/vcard/EntryCommitter.java)28
-rw-r--r--core/java/android/pim/vcard/VCardEntryConstructor.java (renamed from core/java/android/pim/vcard/VCardDataBuilder.java)168
-rw-r--r--core/java/android/pim/vcard/VCardEntryCounter.java21
-rw-r--r--core/java/android/pim/vcard/VCardEntryHandler.java (renamed from core/java/android/pim/vcard/EntryHandler.java)12
-rw-r--r--core/java/android/pim/vcard/VCardInterpreter.java102
-rw-r--r--core/java/android/pim/vcard/VCardInterpreterCollection.java (renamed from core/java/android/pim/vcard/VCardBuilderCollection.java)57
-rw-r--r--core/java/android/pim/vcard/VCardParser.java51
-rw-r--r--core/java/android/pim/vcard/VCardParser_V21.java367
-rw-r--r--core/java/android/pim/vcard/VCardParser_V30.java113
-rw-r--r--core/java/android/pim/vcard/VCardSourceDetector.java46
-rw-r--r--core/java/android/pim/vcard/VCardUtils.java896
-rw-r--r--core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java27
-rw-r--r--core/java/android/preference/EditTextPreference.java3
-rw-r--r--core/java/android/preference/Preference.java27
-rw-r--r--core/java/android/preference/PreferenceGroupAdapter.java83
-rw-r--r--core/java/android/provider/Browser.java24
-rw-r--r--core/java/android/provider/Calendar.java472
-rw-r--r--core/java/android/provider/Checkin.java57
-rw-r--r--core/java/android/provider/Contacts.java66
-rw-r--r--core/java/android/provider/ContactsContract.java557
-rw-r--r--core/java/android/provider/Gmail.java2467
-rw-r--r--core/java/android/provider/Im.java2352
-rw-r--r--core/java/android/provider/MediaStore.java195
-rw-r--r--core/java/android/provider/Settings.java1195
-rw-r--r--core/java/android/provider/SubscribedFeeds.java209
-rw-r--r--core/java/android/provider/Telephony.java21
-rw-r--r--core/java/android/server/BluetoothA2dpService.java26
-rw-r--r--core/java/android/server/BluetoothService.java21
-rw-r--r--core/java/android/server/data/BuildData.java89
-rw-r--r--core/java/android/server/data/CrashData.java145
-rw-r--r--core/java/android/server/data/StackTraceElementData.java80
-rw-r--r--core/java/android/server/data/ThrowableData.java138
-rwxr-xr-xcore/java/android/server/data/package.html5
-rw-r--r--core/java/android/server/search/SearchManagerService.java6
-rw-r--r--core/java/android/server/search/Searchables.java1
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java4
-rwxr-xr-xcore/java/android/speech/tts/ITts.aidl4
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java100
-rw-r--r--core/java/android/text/AutoText.java2
-rw-r--r--core/java/android/text/Html.java2
-rw-r--r--core/java/android/text/Layout.java15
-rw-r--r--core/java/android/text/StaticLayout.java9
-rw-r--r--core/java/android/text/TextUtils.java22
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java200
-rw-r--r--core/java/android/text/method/Touch.java20
-rw-r--r--core/java/android/text/style/LeadingMarginSpan.java5
-rw-r--r--core/java/android/text/util/Linkify.java10
-rw-r--r--core/java/android/text/util/Regex.java204
-rw-r--r--core/java/android/text/util/Rfc822InputFilter.java58
-rw-r--r--core/java/android/text/util/Rfc822Validator.java132
-rw-r--r--core/java/android/util/EventLog.java294
-rw-r--r--core/java/android/util/EventLogTags.java56
-rw-r--r--core/java/android/util/Log.java56
-rw-r--r--core/java/android/util/TimeUtils.java2
-rw-r--r--core/java/android/util/XmlPullAttributes.java2
-rw-r--r--core/java/android/view/View.java151
-rw-r--r--core/java/android/view/ViewConfiguration.java4
-rw-r--r--core/java/android/view/ViewDebug.java6
-rw-r--r--core/java/android/view/ViewGroup.java55
-rw-r--r--core/java/android/view/ViewRoot.java7
-rw-r--r--core/java/android/view/ViewStub.java8
-rw-r--r--core/java/android/view/Window.java2
-rw-r--r--core/java/android/view/WindowManager.java53
-rw-r--r--core/java/android/view/WindowManagerPolicy.java37
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java132
-rw-r--r--core/java/android/webkit/BrowserFrame.java143
-rw-r--r--core/java/android/webkit/ByteArrayBuilder.java125
-rw-r--r--core/java/android/webkit/CacheManager.java63
-rw-r--r--core/java/android/webkit/CallbackProxy.java95
-rw-r--r--core/java/android/webkit/CookieSyncManager.java2
-rw-r--r--core/java/android/webkit/DateSorter.java28
-rw-r--r--core/java/android/webkit/DebugFlags.java2
-rw-r--r--core/java/android/webkit/FileLoader.java89
-rw-r--r--core/java/android/webkit/FrameLoader.java12
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java72
-rw-r--r--core/java/android/webkit/HttpDateTime.java6
-rw-r--r--core/java/android/webkit/LoadListener.java177
-rw-r--r--core/java/android/webkit/MimeTypeMap.java3
-rw-r--r--core/java/android/webkit/Network.java34
-rw-r--r--core/java/android/webkit/PluginActivity.java67
-rw-r--r--core/java/android/webkit/PluginFullScreenHolder.java133
-rw-r--r--core/java/android/webkit/PluginManager.java65
-rw-r--r--core/java/android/webkit/PluginUtil.java59
-rw-r--r--core/java/android/webkit/URLUtil.java27
-rw-r--r--core/java/android/webkit/ViewManager.java24
-rw-r--r--core/java/android/webkit/WebChromeClient.java10
-rw-r--r--core/java/android/webkit/WebSettings.java18
-rw-r--r--core/java/android/webkit/WebStorage.java4
-rw-r--r--core/java/android/webkit/WebTextView.java255
-rw-r--r--core/java/android/webkit/WebView.java1815
-rw-r--r--core/java/android/webkit/WebViewClient.java4
-rw-r--r--core/java/android/webkit/WebViewCore.java432
-rw-r--r--core/java/android/webkit/WebViewDatabase.java24
-rw-r--r--core/java/android/widget/AbsListView.java29
-rw-r--r--core/java/android/widget/AbsSeekBar.java37
-rw-r--r--core/java/android/widget/AbsSpinner.java2
-rw-r--r--core/java/android/widget/AbsoluteLayout.java4
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java48
-rw-r--r--core/java/android/widget/CheckedTextView.java10
-rw-r--r--core/java/android/widget/DatePicker.java4
-rw-r--r--core/java/android/widget/ExpandableListView.java34
-rw-r--r--core/java/android/widget/FrameLayout.java10
-rw-r--r--core/java/android/widget/GridView.java12
-rw-r--r--core/java/android/widget/ImageView.java14
-rw-r--r--core/java/android/widget/LinearLayout.java36
-rw-r--r--core/java/android/widget/ListView.java37
-rw-r--r--core/java/android/widget/MediaController.java4
-rw-r--r--core/java/android/widget/PopupWindow.java10
-rw-r--r--core/java/android/widget/QuickContactBadge.java34
-rw-r--r--core/java/android/widget/RelativeLayout.java21
-rw-r--r--core/java/android/widget/RemoteViews.java40
-rw-r--r--core/java/android/widget/ScrollView.java3
-rw-r--r--core/java/android/widget/SimpleAdapter.java18
-rw-r--r--core/java/android/widget/SimpleCursorAdapter.java31
-rw-r--r--core/java/android/widget/SlidingDrawer.java10
-rw-r--r--core/java/android/widget/TabHost.java9
-rw-r--r--core/java/android/widget/TabWidget.java14
-rw-r--r--core/java/android/widget/TableLayout.java16
-rw-r--r--core/java/android/widget/TableRow.java12
-rw-r--r--core/java/android/widget/TextView.java6
-rw-r--r--core/java/android/widget/TimePicker.java3
-rw-r--r--core/java/android/widget/ViewSwitcher.java2
-rw-r--r--core/java/android/widget/ZoomButtonsController.java2
-rw-r--r--core/java/com/android/internal/app/AlertController.java14
-rw-r--r--core/java/com/android/internal/app/ExternalMediaFormatActivity.java2
-rw-r--r--core/java/com/android/internal/database/ArrayListCursor.java171
-rw-r--r--core/java/com/android/internal/logging/AndroidHandler.java65
-rw-r--r--core/java/com/android/internal/net/DbSSLSessionCache.java289
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java34
-rw-r--r--core/java/com/android/internal/os/BinderInternal.java7
-rw-r--r--core/java/com/android/internal/os/IDropBoxManagerService.aidl42
-rw-r--r--core/java/com/android/internal/os/LoggingPrintStream.java81
-rw-r--r--core/java/com/android/internal/os/PowerProfile.java2
-rw-r--r--core/java/com/android/internal/os/RecoverySystem.java128
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java197
-rw-r--r--core/java/com/android/internal/util/FastXmlSerializer.java365
-rw-r--r--core/java/com/android/internal/util/HanziToPinyin.java455
-rw-r--r--core/java/com/android/internal/util/HierarchicalState.java75
-rw-r--r--core/java/com/android/internal/util/HierarchicalStateMachine.java1164
-rw-r--r--core/java/com/android/internal/util/ProcessedMessages.java198
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java796
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java2
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java175
-rw-r--r--core/java/com/android/internal/widget/NumberPicker.java411
-rw-r--r--core/java/com/android/internal/widget/NumberPickerButton.java86
-rw-r--r--core/java/com/android/internal/widget/SlidingTab.java5
-rw-r--r--core/java/com/android/internal/widget/VerticalTextSpinner.java467
-rw-r--r--core/java/com/google/android/collect/Sets.java79
-rw-r--r--core/java/com/google/android/gdata/client/AndroidGDataClient.java508
-rw-r--r--core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java31
-rw-r--r--core/java/com/google/android/gdata/client/QueryParamsImpl.java102
-rw-r--r--core/java/com/google/android/gdata2/client/AndroidGDataClient.java603
-rw-r--r--core/java/com/google/android/gdata2/client/AndroidXmlParserFactory.java31
-rw-r--r--core/java/com/google/android/gdata2/client/QueryParamsImpl.java99
-rw-r--r--core/java/com/google/android/mms/pdu/PduPersister.java5
-rw-r--r--core/java/com/google/android/net/GoogleHttpClient.java399
-rw-r--r--core/java/com/google/android/net/NetworkStatsEntity.java85
-rw-r--r--core/java/com/google/android/net/SSLClientSessionCacheFactory.java62
-rw-r--r--core/java/com/google/android/net/UrlRules.java236
-rw-r--r--core/java/com/google/android/util/GoogleWebContentHelper.java281
-rw-r--r--core/java/com/google/android/util/SimplePullParser.java391
282 files changed, 14595 insertions, 23813 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 440668f..d5a9b02 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -16,39 +16,44 @@
package android.accounts;
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
-import android.content.pm.PackageInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.RegisteredServicesCacheListener;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.Binder;
import android.os.SystemProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.app.PendingIntent;
-import android.app.NotificationManager;
-import android.app.Notification;
-import android.Manifest;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -58,8 +63,9 @@ import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.R;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.TelephonyIntents;
/**
* A system service that provides account, password, and authtoken management for all
@@ -90,11 +96,8 @@ public class AccountManagerService
// Messages that can be sent on mHandler
private static final int MESSAGE_TIMED_OUT = 3;
- private static final int MESSAGE_CONNECTED = 7;
- private static final int MESSAGE_DISCONNECTED = 8;
private final AccountAuthenticatorCache mAuthenticatorCache;
- private final AuthenticatorBindHelper mBindHelper;
private final DatabaseHelper mOpenHelper;
private final SimWatcher mSimWatcher;
@@ -220,11 +223,38 @@ public class AccountManagerService
mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
mAuthenticatorCache.setListener(this, null /* Handler */);
- mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
- MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
mSimWatcher = new SimWatcher(mContext);
sThis.set(this);
+
+ validateAccounts();
+ }
+
+ private void validateAccounts() {
+ boolean accountDeleted = false;
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor cursor = db.query(TABLE_ACCOUNTS,
+ new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
+ null, null, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final long accountId = cursor.getLong(0);
+ final String accountType = cursor.getString(1);
+ final String accountName = cursor.getString(2);
+ if (mAuthenticatorCache.getServiceInfo(AuthenticatorDescription.newKey(accountType))
+ == null) {
+ Log.d(TAG, "deleting account " + accountName + " because type "
+ + accountType + " no longer has a registered authenticator");
+ db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
+ accountDeleted = true;
+ }
+ }
+ } finally {
+ cursor.close();
+ if (accountDeleted) {
+ sendAccountsChangedBroadcast();
+ }
+ }
}
public void onServiceChanged(AuthenticatorDescription desc, boolean removed) {
@@ -1075,7 +1105,7 @@ public class AccountManagerService
}
private abstract class Session extends IAccountAuthenticatorResponse.Stub
- implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient {
+ implements IBinder.DeathRecipient, ServiceConnection {
IAccountManagerResponse mResponse;
final String mAccountType;
final boolean mExpectActivityLaunch;
@@ -1157,7 +1187,7 @@ public class AccountManagerService
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
}
- if (!mBindHelper.bind(mAccountType, this)) {
+ if (!bindToAuthenticator(mAccountType)) {
Log.d(TAG, "bind attempt failed for " + toDebugString());
onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
}
@@ -1166,7 +1196,7 @@ public class AccountManagerService
private void unbind() {
if (mAuthenticator != null) {
mAuthenticator = null;
- mBindHelper.unbind(this);
+ mContext.unbindService(this);
}
}
@@ -1179,7 +1209,7 @@ public class AccountManagerService
mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
- public void onConnected(IBinder service) {
+ public void onServiceConnected(ComponentName name, IBinder service) {
mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
try {
run();
@@ -1189,9 +1219,7 @@ public class AccountManagerService
}
}
- public abstract void run() throws RemoteException;
-
- public void onDisconnected() {
+ public void onServiceDisconnected(ComponentName name) {
mAuthenticator = null;
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1200,6 +1228,8 @@ public class AccountManagerService
}
}
+ public abstract void run() throws RemoteException;
+
public void onTimedOut() {
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1269,6 +1299,39 @@ public class AccountManagerService
}
}
}
+
+ /**
+ * find the component name for the authenticator and initiate a bind
+ * if no authenticator or the bind fails then return false, otherwise return true
+ */
+ private boolean bindToAuthenticator(String authenticatorType) {
+ AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
+ mAuthenticatorCache.getServiceInfo(
+ AuthenticatorDescription.newKey(authenticatorType));
+ if (authenticatorInfo == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "there is no authenticator for " + authenticatorType
+ + ", bailing out");
+ }
+ return false;
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+ intent.setComponent(authenticatorInfo.componentName);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
+ }
+ if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
+ }
+ return false;
+ }
+
+
+ return true;
+ }
}
private class MessageHandler extends Handler {
@@ -1277,9 +1340,6 @@ public class AccountManagerService
}
public void handleMessage(Message msg) {
- if (mBindHelper.handleMessage(msg)) {
- return;
- }
switch (msg.what) {
case MESSAGE_TIMED_OUT:
Session session = (Session)msg.obj;
@@ -1292,9 +1352,20 @@ public class AccountManagerService
}
}
+ private static String getDatabaseName() {
+ if(Environment.isEncryptedFilesystemEnabled()) {
+ // Hard-coded path in case of encrypted file system
+ return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME;
+ } else {
+ // Regular path in case of non-encrypted file system
+ return DATABASE_NAME;
+ }
+ }
+
private class DatabaseHelper extends SQLiteOpenHelper {
+
public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ super(context, AccountManagerService.getDatabaseName(), null, DATABASE_VERSION);
}
@Override
@@ -1419,16 +1490,58 @@ public class AccountManagerService
*/
@Override
public void onReceive(Context context, Intent intent) {
- // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged.
- String imsi = ((TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE)).getSubscriberId();
+ // Check IMSI on every update; nothing happens if the IMSI
+ // is missing or unchanged.
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (telephonyManager == null) {
+ Log.w(TAG, "failed to get TelephonyManager");
+ return;
+ }
+ String imsi = telephonyManager.getSubscriberId();
+
+ // If the subscriber ID is an empty string, don't do anything.
if (TextUtils.isEmpty(imsi)) return;
+ // If the current IMSI matches what's stored, don't do anything.
String storedImsi = getMetaValue("imsi");
-
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi);
}
+ if (imsi.equals(storedImsi)) return;
+
+ // If a CDMA phone is unprovisioned, getSubscriberId()
+ // will return a different value, but we *don't* erase the
+ // passwords. We only erase them if it has a different
+ // subscriber ID once it's provisioned.
+ if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ IBinder service = ServiceManager.checkService(Context.TELEPHONY_SERVICE);
+ if (service == null) {
+ Log.w(TAG, "call to checkService(TELEPHONY_SERVICE) failed");
+ return;
+ }
+ ITelephony telephony = ITelephony.Stub.asInterface(service);
+ if (telephony == null) {
+ Log.w(TAG, "failed to get ITelephony interface");
+ return;
+ }
+ boolean needsProvisioning;
+ try {
+ needsProvisioning = telephony.getCdmaNeedsProvisioning();
+ } catch (RemoteException e) {
+ Log.w(TAG, "exception while checking provisioning", e);
+ // default to NOT wiping out the passwords
+ needsProvisioning = true;
+ }
+ if (needsProvisioning) {
+ // if the phone needs re-provisioning, don't do anything.
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "current IMSI=" + imsi + " (needs provisioning); stored IMSI=" +
+ storedImsi);
+ }
+ return;
+ }
+ }
if (!imsi.equals(storedImsi) && !TextUtils.isEmpty(storedImsi)) {
Log.w(TAG, "wiping all passwords and authtokens because IMSI changed ("
@@ -1572,6 +1685,7 @@ public class AccountManagerService
}
private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
+ final boolean inSystemImage = inSystemImage(callerUid);
final boolean fromAuthenticator = account != null
&& hasAuthenticatorUid(account.type, callerUid);
final boolean hasExplicitGrants = account != null
@@ -1582,7 +1696,7 @@ public class AccountManagerService
+ ": is authenticator? " + fromAuthenticator
+ ", has explicit permission? " + hasExplicitGrants);
}
- return fromAuthenticator || hasExplicitGrants || inSystemImage(callerUid);
+ return fromAuthenticator || hasExplicitGrants || inSystemImage;
}
private boolean hasAuthenticatorUid(String accountType, int callingUid) {
diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java
deleted file mode 100644
index 2ca1f0e..0000000
--- a/core/java/android/accounts/AuthenticatorBindHelper.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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 android.accounts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-/**
- * A helper object that simplifies binding to Account Authenticators. It uses the
- * {@link AccountAuthenticatorCache} to find the component name of the authenticators,
- * allowing the user to bind by account name. It also allows multiple, simultaneous binds
- * to the same authenticator, with each bind call guaranteed to return either
- * {@link Callback#onConnected} or {@link Callback#onDisconnected} if the bind() call
- * itself succeeds, even if the authenticator is already bound internally.
- * @hide
- */
-public class AuthenticatorBindHelper {
- private static final String TAG = "Accounts";
- private final Handler mHandler;
- private final Context mContext;
- private final int mMessageWhatConnected;
- private final int mMessageWhatDisconnected;
- private final Map<String, MyServiceConnection> mServiceConnections = Maps.newHashMap();
- private final Map<String, ArrayList<Callback>> mServiceUsers = Maps.newHashMap();
- private final AccountAuthenticatorCache mAuthenticatorCache;
-
- public AuthenticatorBindHelper(Context context,
- AccountAuthenticatorCache authenticatorCache, Handler handler,
- int messageWhatConnected, int messageWhatDisconnected) {
- mContext = context;
- mHandler = handler;
- mAuthenticatorCache = authenticatorCache;
- mMessageWhatConnected = messageWhatConnected;
- mMessageWhatDisconnected = messageWhatDisconnected;
- }
-
- public interface Callback {
- void onConnected(IBinder service);
- void onDisconnected();
- }
-
- public boolean bind(String authenticatorType, Callback callback) {
- // if the authenticator is connecting or connected then return true
- synchronized (mServiceConnections) {
- if (mServiceConnections.containsKey(authenticatorType)) {
- MyServiceConnection connection = mServiceConnections.get(authenticatorType);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "service connection already exists for " + authenticatorType);
- }
- mServiceUsers.get(authenticatorType).add(callback);
- if (connection.mService != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service is connected, scheduling a connected message for "
- + authenticatorType);
- }
- connection.scheduleCallbackConnectedMessage(callback);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service is *not* connected, waiting for for "
- + authenticatorType);
- }
- }
- return true;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no service connection for " + authenticatorType);
- }
-
- // otherwise find the component name for the authenticator and initiate a bind
- // if no authenticator or the bind fails then return false, otherwise return true
- AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
- mAuthenticatorCache.getServiceInfo(
- AuthenticatorDescription.newKey(authenticatorType));
- if (authenticatorInfo == null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no authenticator for " + authenticatorType
- + ", bailing out");
- }
- return false;
- }
-
- MyServiceConnection connection = new MyServiceConnection(authenticatorType);
-
- Intent intent = new Intent();
- intent.setAction("android.accounts.AccountAuthenticator");
- intent.setComponent(authenticatorInfo.componentName);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
- }
- if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
- }
- return false;
- }
-
- mServiceConnections.put(authenticatorType, connection);
- mServiceUsers.put(authenticatorType, Lists.newArrayList(callback));
- return true;
- }
- }
-
- public void unbind(Callback callbackToUnbind) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "unbinding callback " + callbackToUnbind);
- }
- synchronized (mServiceConnections) {
- for (Map.Entry<String, ArrayList<Callback>> entry : mServiceUsers.entrySet()) {
- final String authenticatorType = entry.getKey();
- final ArrayList<Callback> serviceUsers = entry.getValue();
- for (Callback callback : serviceUsers) {
- if (callback == callbackToUnbind) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "found callback in service" + authenticatorType);
- }
- serviceUsers.remove(callbackToUnbind);
- if (serviceUsers.isEmpty()) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there are no more callbacks for service "
- + authenticatorType + ", unbinding service");
- }
- unbindFromServiceLocked(authenticatorType);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "leaving service " + authenticatorType
- + " around since there are still callbacks using it");
- }
- }
- return;
- }
- }
- }
- Log.e(TAG, "did not find callback " + callbackToUnbind + " in any of the services");
- }
- }
-
- /**
- * You must synchronized on mServiceConnections before calling this
- */
- private void unbindFromServiceLocked(String authenticatorType) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "unbindService from " + authenticatorType);
- }
- mContext.unbindService(mServiceConnections.get(authenticatorType));
- mServiceUsers.remove(authenticatorType);
- mServiceConnections.remove(authenticatorType);
- }
-
- private class ConnectedMessagePayload {
- public final IBinder mService;
- public final Callback mCallback;
- public ConnectedMessagePayload(IBinder service, Callback callback) {
- mService = service;
- mCallback = callback;
- }
- }
-
- private class MyServiceConnection implements ServiceConnection {
- private final String mAuthenticatorType;
- private IBinder mService = null;
-
- public MyServiceConnection(String authenticatorType) {
- mAuthenticatorType = authenticatorType;
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onServiceConnected for account type " + mAuthenticatorType);
- }
- // post a message for each service user to tell them that the service is connected
- synchronized (mServiceConnections) {
- mService = service;
- for (Callback callback : mServiceUsers.get(mAuthenticatorType)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service became connected, scheduling a connected "
- + "message for " + mAuthenticatorType);
- }
- scheduleCallbackConnectedMessage(callback);
- }
- }
- }
-
- private void scheduleCallbackConnectedMessage(Callback callback) {
- final ConnectedMessagePayload payload =
- new ConnectedMessagePayload(mService, callback);
- mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget();
- }
-
- public void onServiceDisconnected(ComponentName name) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onServiceDisconnected for account type " + mAuthenticatorType);
- }
- // post a message for each service user to tell them that the service is disconnected,
- // and unbind from the service.
- synchronized (mServiceConnections) {
- final ArrayList<Callback> callbackList = mServiceUsers.get(mAuthenticatorType);
- if (callbackList != null) {
- for (Callback callback : callbackList) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service became disconnected, scheduling a "
- + "disconnected message for "
- + mAuthenticatorType);
- }
- mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget();
- }
- unbindFromServiceLocked(mAuthenticatorType);
- }
- }
- }
- }
-
- boolean handleMessage(Message message) {
- if (message.what == mMessageWhatConnected) {
- ConnectedMessagePayload payload = (ConnectedMessagePayload)message.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "notifying callback " + payload.mCallback + " that it is connected");
- }
- payload.mCallback.onConnected(payload.mService);
- return true;
- } else if (message.what == mMessageWhatDisconnected) {
- Callback callback = (Callback)message.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "notifying callback " + callback + " that it is disconnected");
- }
- callback.onDisconnected();
- return true;
- } else {
- return false;
- }
- }
-}
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index 4282c1b..f4b7258 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -18,15 +18,16 @@ package android.accounts;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
+import android.widget.LinearLayout;
+import android.widget.ImageView;
import android.view.View;
import android.view.LayoutInflater;
-import android.view.ViewGroup;
+import android.view.Window;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.text.TextUtils;
+import android.graphics.drawable.Drawable;
import com.android.internal.R;
/**
@@ -44,62 +45,68 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
private String mAuthTokenType;
private int mUid;
private Bundle mResultBundle = null;
+ protected LayoutInflater mInflater;
protected void onCreate(Bundle savedInstanceState) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
- getWindow().setContentView(R.layout.grant_credentials_permission);
- mAccount = getIntent().getExtras().getParcelable(EXTRAS_ACCOUNT);
- mAuthTokenType = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_TYPE);
- mUid = getIntent().getExtras().getInt(EXTRAS_REQUESTING_UID);
- final String accountTypeLabel =
- getIntent().getExtras().getString(EXTRAS_ACCOUNT_TYPE_LABEL);
- final String[] packages = getIntent().getExtras().getStringArray(EXTRAS_PACKAGES);
+ setContentView(R.layout.grant_credentials_permission);
- findViewById(R.id.allow).setOnClickListener(this);
- findViewById(R.id.deny).setOnClickListener(this);
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- TextView messageView = (TextView) getWindow().findViewById(R.id.message);
- String authTokenLabel = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_LABEL);
- if (TextUtils.isEmpty(authTokenLabel)) {
- CharSequence grantCredentialsPermissionFormat = getResources().getText(
- R.string.grant_credentials_permission_message_desc);
- messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
- mAccount.name, accountTypeLabel));
- } else {
- CharSequence grantCredentialsPermissionFormat = getResources().getText(
- R.string.grant_credentials_permission_message_with_authtokenlabel_desc);
- messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
- authTokenLabel, mAccount.name, accountTypeLabel));
- }
+ final Bundle extras = getIntent().getExtras();
+ mAccount = extras.getParcelable(EXTRAS_ACCOUNT);
+ mAuthTokenType = extras.getString(EXTRAS_AUTH_TOKEN_TYPE);
+ mUid = extras.getInt(EXTRAS_REQUESTING_UID);
+ final String accountTypeLabel = extras.getString(EXTRAS_ACCOUNT_TYPE_LABEL);
+ final String[] packages = extras.getStringArray(EXTRAS_PACKAGES);
+ final String authTokenLabel = extras.getString(EXTRAS_AUTH_TOKEN_LABEL);
+
+ findViewById(R.id.allow_button).setOnClickListener(this);
+ findViewById(R.id.deny_button).setOnClickListener(this);
+
+ LinearLayout packagesListView = (LinearLayout) findViewById(R.id.packages_list);
- String[] packageLabels = new String[packages.length];
final PackageManager pm = getPackageManager();
- for (int i = 0; i < packages.length; i++) {
+ for (String pkg : packages) {
+ String packageLabel;
try {
- packageLabels[i] =
- pm.getApplicationLabel(pm.getApplicationInfo(packages[i], 0)).toString();
+ packageLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
} catch (PackageManager.NameNotFoundException e) {
- packageLabels[i] = packages[i];
+ packageLabel = pkg;
}
+ packagesListView.addView(newPackageView(packageLabel));
}
- ((ListView) findViewById(R.id.packages_list)).setAdapter(
- new PackagesArrayAdapter(this, packageLabels));
+
+ ((TextView) findViewById(R.id.account_name)).setText(mAccount.name);
+ ((TextView) findViewById(R.id.account_type)).setText(accountTypeLabel);
+ TextView authTokenTypeView = (TextView) findViewById(R.id.authtoken_type);
+ if (TextUtils.isEmpty(authTokenLabel)) {
+ authTokenTypeView.setVisibility(View.GONE);
+ } else {
+ authTokenTypeView.setText(authTokenLabel);
+ }
+ }
+
+ private View newPackageView(String packageLabel) {
+ View view = mInflater.inflate(R.layout.permissions_package_list_item, null);
+ ((TextView) view.findViewById(R.id.package_label)).setText(packageLabel);
+ return view;
}
public void onClick(View v) {
+ final AccountManagerService accountManagerService = AccountManagerService.getSingleton();
switch (v.getId()) {
- case R.id.allow:
- AccountManagerService.getSingleton().grantAppPermission(mAccount, mAuthTokenType,
- mUid);
+ case R.id.allow_button:
+ accountManagerService.grantAppPermission(mAccount, mAuthTokenType, mUid);
Intent result = new Intent();
result.putExtra("retry", true);
setResult(RESULT_OK, result);
setAccountAuthenticatorResult(result.getExtras());
break;
- case R.id.deny:
- AccountManagerService.getSingleton().revokeAppPermission(mAccount, mAuthTokenType,
- mUid);
+ case R.id.deny_button:
+ accountManagerService.revokeAppPermission(mAccount, mAuthTokenType, mUid);
setResult(RESULT_CANCELED);
break;
}
@@ -111,63 +118,20 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
}
/**
- * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+ * Sends the result or a {@link AccountManager#ERROR_CODE_CANCELED} error if a
+ * result isn't present.
*/
public void finish() {
Intent intent = getIntent();
- AccountAuthenticatorResponse accountAuthenticatorResponse =
- intent.getParcelableExtra(EXTRAS_RESPONSE);
- if (accountAuthenticatorResponse != null) {
+ AccountAuthenticatorResponse response = intent.getParcelableExtra(EXTRAS_RESPONSE);
+ if (response != null) {
// send the result bundle back if set, otherwise send an error.
if (mResultBundle != null) {
- accountAuthenticatorResponse.onResult(mResultBundle);
+ response.onResult(mResultBundle);
} else {
- accountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ response.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
}
}
super.finish();
}
-
- private static class PackagesArrayAdapter extends ArrayAdapter<String> {
- protected LayoutInflater mInflater;
- private static final int mResource = R.layout.simple_list_item_1;
-
- public PackagesArrayAdapter(Context context, String[] items) {
- super(context, mResource, items);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- static class ViewHolder {
- TextView label;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // A ViewHolder keeps references to children views to avoid unneccessary calls
- // to findViewById() on each row.
- ViewHolder holder;
-
- // When convertView is not null, we can reuse it directly, there is no need
- // to reinflate it. We only inflate a new View when the convertView supplied
- // by ListView is null.
- if (convertView == null) {
- convertView = mInflater.inflate(mResource, null);
-
- // Creates a ViewHolder and store references to the two children views
- // we want to bind data to.
- holder = new ViewHolder();
- holder.label = (TextView) convertView.findViewById(R.id.text1);
-
- convertView.setTag(holder);
- } else {
- // Get the ViewHolder back to get fast access to the TextView
- // and the ImageView.
- holder = (ViewHolder) convertView.getTag();
- }
-
- holder.label.setText(getItem(position));
-
- return convertView;
- }
- }
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 49ebce3..1c3414d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2087,8 +2087,8 @@ public class Activity extends ContextThemeWrapper
event.setPackageName(getPackageName());
LayoutParams params = getWindow().getAttributes();
- boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
- (params.height == LayoutParams.FILL_PARENT);
+ boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) &&
+ (params.height == LayoutParams.MATCH_PARENT);
event.setFullScreen(isFullScreen);
CharSequence title = getTitle();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d709deb..932ad53 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -606,7 +606,7 @@ public class ActivityManager {
public int uid;
/**
- * The tag that was provided when the process crashed.
+ * The activity name associated with the error, if known. May be null.
*/
public String tag;
@@ -621,9 +621,14 @@ public class ActivityManager {
public String longMsg;
/**
- * Raw data about the crash (typically a stack trace).
+ * The stack trace where the error originated. May be null.
*/
- public byte[] crashData;
+ public String stackTrace;
+
+ /**
+ * to be deprecated: This value will always be null.
+ */
+ public byte[] crashData = null;
public ProcessErrorStateInfo() {
}
@@ -640,8 +645,7 @@ public class ActivityManager {
dest.writeString(tag);
dest.writeString(shortMsg);
dest.writeString(longMsg);
- dest.writeInt(crashData == null ? -1 : crashData.length);
- dest.writeByteArray(crashData);
+ dest.writeString(stackTrace);
}
public void readFromParcel(Parcel source) {
@@ -652,13 +656,7 @@ public class ActivityManager {
tag = source.readString();
shortMsg = source.readString();
longMsg = source.readString();
- int cdLen = source.readInt();
- if (cdLen == -1) {
- crashData = null;
- } else {
- crashData = new byte[cdLen];
- source.readByteArray(crashData);
- }
+ stackTrace = source.readString();
}
public static final Creator<ProcessErrorStateInfo> CREATOR =
@@ -892,6 +890,38 @@ public class ActivityManager {
}
/**
+ * @deprecated This is now just a wrapper for
+ * {@link #killBackgroundProcesses(String)}; the previous behavior here
+ * is no longer available to applications because it allows them to
+ * break other applications by removing their alarms, stopping their
+ * services, etc.
+ */
+ @Deprecated
+ public void restartPackage(String packageName) {
+ killBackgroundProcesses(packageName);
+ }
+
+ /**
+ * Have the system immediately kill all background processes associated
+ * with the given package. This is the same as the kernel killing those
+ * processes to reclaim memory; the system will take care of restarting
+ * these processes in the future as needed.
+ *
+ * <p>You must hold the permission
+ * {@link android.Manifest.permission#KILL_BACKGROUND_PROCESSES} to be able to
+ * call this method.
+ *
+ * @param packageName The name of the package whose processes are to
+ * be killed.
+ */
+ public void killBackgroundProcesses(String packageName) {
+ try {
+ ActivityManagerNative.getDefault().killBackgroundProcesses(packageName);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Have the system perform a force stop of everything associated with
* the given application package. All processes that share its uid
* will be killed, all services it has running stopped, all activities
@@ -900,14 +930,18 @@ public class ActivityManager {
* be stopped, notifications removed, etc.
*
* <p>You must hold the permission
- * {@link android.Manifest.permission#RESTART_PACKAGES} to be able to
+ * {@link android.Manifest.permission#FORCE_STOP_PACKAGES} to be able to
* call this method.
*
* @param packageName The name of the package to be stopped.
+ *
+ * @hide This is not available to third party applications due to
+ * it allowing them to break other applications by stopping their
+ * services, removing their alarms, etc.
*/
- public void restartPackage(String packageName) {
+ public void forceStopPackage(String packageName) {
try {
- ActivityManagerNative.getDefault().restartPackage(packageName);
+ ActivityManagerNative.getDefault().forceStopPackage(packageName);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 3b8aee9..09b88ee 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -979,21 +979,26 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case HANDLE_APPLICATION_ERROR_TRANSACTION: {
+ case HANDLE_APPLICATION_CRASH_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder app = data.readStrongBinder();
+ ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data);
+ handleApplicationCrash(app, ci);
+ reply.writeNoException();
+ return true;
+ }
+
+ case HANDLE_APPLICATION_WTF_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder app = data.readStrongBinder();
- int fl = data.readInt();
String tag = data.readString();
- String shortMsg = data.readString();
- String longMsg = data.readString();
- byte[] crashData = data.createByteArray();
- int res = handleApplicationError(app, fl, tag, shortMsg, longMsg,
- crashData);
+ ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data);
+ boolean res = handleApplicationWtf(app, tag, ci);
reply.writeNoException();
- reply.writeInt(res);
+ reply.writeInt(res ? 1 : 0);
return true;
}
-
+
case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int sig = data.readInt();
@@ -1002,10 +1007,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case RESTART_PACKAGE_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
+ case KILL_BACKGROUND_PROCESSES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String packageName = data.readString();
+ killBackgroundProcesses(packageName);
+ reply.writeNoException();
+ return true;
+ }
+
+ case FORCE_STOP_PACKAGE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
String packageName = data.readString();
- restartPackage(packageName);
+ forceStopPackage(packageName);
reply.writeNoException();
return true;
}
@@ -2342,27 +2355,36 @@ class ActivityManagerProxy implements IActivityManager
/* this base class version is never called */
return true;
}
- public int handleApplicationError(IBinder app, int flags,
- String tag, String shortMsg, String longMsg,
- byte[] crashData) throws RemoteException
+ public void handleApplicationCrash(IBinder app,
+ ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(app);
+ crashInfo.writeToParcel(data, 0);
+ mRemote.transact(HANDLE_APPLICATION_CRASH_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+ public boolean handleApplicationWtf(IBinder app, String tag,
+ ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(app);
- data.writeInt(flags);
data.writeString(tag);
- data.writeString(shortMsg);
- data.writeString(longMsg);
- data.writeByteArray(crashData);
- mRemote.transact(HANDLE_APPLICATION_ERROR_TRANSACTION, data, reply, 0);
+ crashInfo.writeToParcel(data, 0);
+ mRemote.transact(HANDLE_APPLICATION_WTF_TRANSACTION, data, reply, 0);
reply.readException();
- int res = reply.readInt();
+ boolean res = reply.readInt() != 0;
reply.recycle();
data.recycle();
return res;
}
-
+
public void signalPersistentProcesses(int sig) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2374,12 +2396,23 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
- public void restartPackage(String packageName) throws RemoteException {
+ public void killBackgroundProcesses(String packageName) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ mRemote.transact(KILL_BACKGROUND_PROCESSES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void forceStopPackage(String packageName) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(packageName);
- mRemote.transact(RESTART_PACKAGE_TRANSACTION, data, reply, 0);
+ mRemote.transact(FORCE_STOP_PACKAGE_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 909620d..10fef0d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,7 +40,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.net.http.AndroidHttpClient;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -4303,7 +4302,6 @@ public final class ActivityThread {
private final void attach(boolean system) {
sThreadLocal.set(this);
mSystemThread = system;
- AndroidHttpClient.setThreadBlocked(true);
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>");
RuntimeInit.setApplicationObject(mAppThread.asBinder());
@@ -4333,7 +4331,6 @@ public final class ActivityThread {
private final void detach()
{
- AndroidHttpClient.setThreadBlocked(false);
sThreadLocal.set(null);
}
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 20a579a..2603579 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -40,7 +40,7 @@ import com.android.internal.app.AlertController;
*
* <pre>
* FrameLayout fl = (FrameLayout) findViewById(R.id.body);
- * fl.add(myView, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
+ * fl.add(myView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
* </pre>
*
* <p>The AlertDialog class takes care of automatically setting
diff --git a/core/java/android/app/AliasActivity.java b/core/java/android/app/AliasActivity.java
index 4f91e02..7527a5b 100644
--- a/core/java/android/app/AliasActivity.java
+++ b/core/java/android/app/AliasActivity.java
@@ -26,7 +26,7 @@ import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Xml;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import java.io.IOException;
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index f48f150..d89b877 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -17,7 +17,7 @@
package android.app;
import com.android.internal.policy.PolicyManager;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import com.google.android.collect.Maps;
import org.xmlpull.v1.XmlPullParserException;
@@ -70,6 +70,7 @@ import android.net.wifi.IWifiManager;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
+import android.os.DropBoxManager;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
@@ -93,6 +94,8 @@ import android.view.inputmethod.InputMethodManager;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
+import com.android.internal.os.IDropBoxManagerService;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -182,6 +185,7 @@ class ApplicationContext extends Context {
private ClipboardManager mClipboardManager = null;
private boolean mRestricted;
private AccountManager mAccountManager; // protected by mSync
+ private DropBoxManager mDropBoxManager = null;
private final Object mSync = new Object();
@@ -462,14 +466,7 @@ class ApplicationContext extends Context {
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
- File dir = getDatabasesDir();
- if (!dir.isDirectory() && dir.mkdir()) {
- FileUtils.setPermissions(dir.getPath(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
- }
-
- File f = makeFilename(dir, name);
+ File f = validateFilePath(name, true);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory);
setFilePermissionsFromMode(f.getPath(), mode, 0);
return db;
@@ -478,7 +475,7 @@ class ApplicationContext extends Context {
@Override
public boolean deleteDatabase(String name) {
try {
- File f = makeFilename(getDatabasesDir(), name);
+ File f = validateFilePath(name, false);
return f.delete();
} catch (Exception e) {
}
@@ -487,7 +484,7 @@ class ApplicationContext extends Context {
@Override
public File getDatabasePath(String name) {
- return makeFilename(getDatabasesDir(), name);
+ return validateFilePath(name, false);
}
@Override
@@ -896,6 +893,8 @@ class ApplicationContext extends Context {
return getClipboardManager();
} else if (WALLPAPER_SERVICE.equals(name)) {
return getWallpaperManager();
+ } else if (DROPBOX_SERVICE.equals(name)) {
+ return getDropBoxManager();
}
return null;
@@ -1045,7 +1044,7 @@ class ApplicationContext extends Context {
}
return mVibrator;
}
-
+
private AudioManager getAudioManager()
{
if (mAudioManager == null) {
@@ -1054,6 +1053,17 @@ class ApplicationContext extends Context {
return mAudioManager;
}
+ private DropBoxManager getDropBoxManager() {
+ synchronized (mSync) {
+ if (mDropBoxManager == null) {
+ IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
+ IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
+ mDropBoxManager = new DropBoxManager(service);
+ }
+ }
+ return mDropBoxManager;
+ }
+
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
@@ -1437,12 +1447,35 @@ class ApplicationContext extends Context {
FileUtils.setPermissions(name, perms, -1, -1);
}
+ private File validateFilePath(String name, boolean createDirectory) {
+ File dir;
+ File f;
+
+ if (name.charAt(0) == File.separatorChar) {
+ String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
+ dir = new File(dirPath);
+ name = name.substring(name.lastIndexOf(File.separatorChar));
+ f = new File(dir, name);
+ } else {
+ dir = getDatabasesDir();
+ f = makeFilename(dir, name);
+ }
+
+ if (createDirectory && !dir.isDirectory() && dir.mkdir()) {
+ FileUtils.setPermissions(dir.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ }
+
+ return f;
+ }
+
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
throw new IllegalArgumentException(
- "File " + name + " contains a path separator");
+ "File " + name + " contains a path separator");
}
// ----------------------------------------------------------------------
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index aeae5f9..a4b692f 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -19,6 +19,8 @@ package android.app;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Printer;
+import java.io.PrintWriter;
+import java.io.StringWriter;
/**
* Describes an application error.
@@ -187,6 +189,32 @@ public class ApplicationErrorReport implements Parcelable {
}
/**
+ * Create an instance of CrashInfo initialized from an exception.
+ */
+ public CrashInfo(Throwable tr) {
+ StringWriter sw = new StringWriter();
+ tr.printStackTrace(new PrintWriter(sw));
+ stackTrace = sw.toString();
+ exceptionMessage = tr.getMessage();
+
+ // Populate fields with the "root cause" exception
+ while (tr.getCause() != null) {
+ tr = tr.getCause();
+ String msg = tr.getMessage();
+ if (msg != null && msg.length() > 0) {
+ exceptionMessage = msg;
+ }
+ }
+
+ exceptionClassName = tr.getClass().getName();
+ StackTraceElement trace = tr.getStackTrace()[0];
+ throwFileName = trace.getFileName();
+ throwClassName = trace.getClassName();
+ throwMethodName = trace.getMethodName();
+ throwLineNumber = trace.getLineNumber();
+ }
+
+ /**
* Create an instance of CrashInfo initialized from a Parcel.
*/
public CrashInfo(Parcel in) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 58e8b32..fa5d4a8 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -668,8 +668,8 @@ public class Dialog implements DialogInterface, Window.Callback,
event.setPackageName(mContext.getPackageName());
LayoutParams params = getWindow().getAttributes();
- boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
- (params.height == LayoutParams.FILL_PARENT);
+ boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) &&
+ (params.height == LayoutParams.MATCH_PARENT);
event.setFullScreen(isFullScreen);
return false;
diff --git a/core/java/android/app/ExpandableListActivity.java b/core/java/android/app/ExpandableListActivity.java
index a2e048f..9651078 100644
--- a/core/java/android/app/ExpandableListActivity.java
+++ b/core/java/android/app/ExpandableListActivity.java
@@ -65,21 +65,21 @@ import java.util.Map;
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
* &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation=&quot;vertical&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:paddingLeft=&quot;8dp&quot;
* android:paddingRight=&quot;8dp&quot;&gt;
*
* &lt;ExpandableListView android:id=&quot;@id/android:list&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:background=&quot;#00FF00&quot;
* android:layout_weight=&quot;1&quot;
* android:drawSelectorOnTop=&quot;false&quot;/&gt;
*
* &lt;TextView android:id=&quot;@id/android:empty&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:background=&quot;#FF0000&quot;
* android:text=&quot;No data&quot;/&gt;
* &lt;/LinearLayout&gt;
@@ -114,19 +114,19 @@ import java.util.Map;
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
* &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;
* android:orientation=&quot;vertical&quot;&gt;
*
* &lt;TextView android:id=&quot;@+id/text1&quot;
* android:textSize=&quot;16sp&quot;
* android:textStyle=&quot;bold&quot;
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
*
* &lt;TextView android:id=&quot;@+id/text2&quot;
* android:textSize=&quot;16sp&quot;
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
* &lt;/LinearLayout&gt;
* </pre>
diff --git a/core/java/android/app/IActivityController.aidl b/core/java/android/app/IActivityController.aidl
index 8f6b252..c76a517 100644
--- a/core/java/android/app/IActivityController.aidl
+++ b/core/java/android/app/IActivityController.aidl
@@ -43,8 +43,9 @@ interface IActivityController
* normal error recovery (app crash dialog) to occur, false to kill
* it immediately.
*/
- boolean appCrashed(String processName, int pid, String shortMsg,
- String longMsg, in byte[] crashData);
+ boolean appCrashed(String processName, int pid,
+ String shortMsg, String longMsg,
+ long timeMillis, String stackTrace);
/**
* An application process is not responding. Return 0 to show the "app
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 9f505ac..016d465 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -216,7 +216,8 @@ public interface IActivityManager extends IInterface {
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException;
- public void restartPackage(final String packageName) throws RemoteException;
+ public void killBackgroundProcesses(final String packageName) throws RemoteException;
+ public void forceStopPackage(final String packageName) throws RemoteException;
// Note: probably don't want to allow applications access to these.
public void goingToSleep() throws RemoteException;
@@ -242,11 +243,10 @@ public interface IActivityManager extends IInterface {
// Special low-level communication with activity manager.
public void startRunning(String pkg, String cls, String action,
String data) throws RemoteException;
- // Returns 1 if the user wants to debug.
- public int handleApplicationError(IBinder app,
- int flags, /* 1 == can debug */
- String tag, String shortMsg, String longMsg,
- byte[] crashData) throws RemoteException;
+ public void handleApplicationCrash(IBinder app,
+ ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
+ public boolean handleApplicationWtf(IBinder app, String tag,
+ ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
/*
* This will deliver the specified signal to all the persistent processes. Currently only
@@ -351,7 +351,7 @@ public interface IActivityManager extends IInterface {
// Please keep these transaction codes the same -- they are also
// sent by C++ code.
int START_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
- int HANDLE_APPLICATION_ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
+ int HANDLE_APPLICATION_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
int START_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
int UNHANDLED_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
int OPEN_CONTENT_URI_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
@@ -425,7 +425,7 @@ public interface IActivityManager extends IInterface {
int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+75;
int GET_PROCESSES_IN_ERROR_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+76;
int CLEAR_APP_DATA_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+77;
- int RESTART_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78;
+ int FORCE_STOP_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78;
int KILL_PIDS_FOR_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79;
int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80;
int REPORT_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
@@ -448,4 +448,6 @@ public interface IActivityManager extends IInterface {
int KILL_APPLICATION_PROCESS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+98;
int START_ACTIVITY_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+99;
int OVERRIDE_PENDING_TRANSITION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+100;
+ int HANDLE_APPLICATION_WTF_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+101;
+ int KILL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+102;
}
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index a7d6378..0920467 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -16,11 +16,11 @@
package android.app;
+import android.app.SearchableInfo;
import android.app.ISearchManagerCallback;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.server.search.SearchableInfo;
/** @hide */
interface ISearchManager {
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
index 19b99c8..4b4cc05 100644
--- a/core/java/android/app/ListActivity.java
+++ b/core/java/android/app/ListActivity.java
@@ -56,21 +56,21 @@ import android.widget.ListView;
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
* &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation=&quot;vertical&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:paddingLeft=&quot;8dp&quot;
* android:paddingRight=&quot;8dp&quot;&gt;
*
* &lt;ListView android:id=&quot;@id/android:list&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:background=&quot;#00FF00&quot;
* android:layout_weight=&quot;1&quot;
* android:drawSelectorOnTop=&quot;false&quot;/&gt;
*
* &lt;TextView id=&quot;@id/android:empty&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:background=&quot;#FF0000&quot;
* android:text=&quot;No data&quot;/&gt;
* &lt;/LinearLayout&gt;
@@ -100,19 +100,19 @@ import android.widget.ListView;
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
* &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;
* android:orientation=&quot;vertical&quot;&gt;
*
* &lt;TextView android:id=&quot;@+id/text1&quot;
* android:textSize=&quot;16sp&quot;
* android:textStyle=&quot;bold&quot;
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
*
* &lt;TextView android:id=&quot;@+id/text2&quot;
* android:textSize=&quot;16sp&quot;
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
* &lt;/LinearLayout&gt;
* </pre>
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index e5a769b..b396396 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -37,13 +37,11 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.Browser;
-import android.server.search.SearchableInfo;
import android.speech.RecognizerIntent;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.text.util.Regex;
import android.util.AndroidRuntimeException;
import android.util.AttributeSet;
import android.util.Log;
@@ -62,13 +60,14 @@ import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
+import com.android.common.Patterns;
+
import java.util.ArrayList;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
@@ -106,7 +105,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// views & widgets
private TextView mBadgeLabel;
- private ImageView mAppIcon;
+ private SearchSourceSelector mSourceSelector;
private SearchAutoComplete mSearchAutoComplete;
private Button mGoButton;
private ImageButton mVoiceButton;
@@ -182,11 +181,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
Window theWindow = getWindow();
WindowManager.LayoutParams lp = theWindow.getAttributes();
lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
- lp.width = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
// taking up the whole window (even when transparent) is less than ideal,
// but necessary to show the popup window until the window manager supports
// having windows anchored by their parent but not clipped by them.
- lp.height = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
theWindow.setAttributes(lp);
@@ -209,7 +208,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
mSearchAutoComplete = (SearchAutoComplete)
findViewById(com.android.internal.R.id.search_src_text);
- mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon);
+ mSourceSelector = new SearchSourceSelector(
+ findViewById(com.android.internal.R.id.search_source_selector));
mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
mSearchPlate = findViewById(com.android.internal.R.id.search_plate);
@@ -606,13 +606,16 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
private void updateSearchAppIcon() {
+ mSourceSelector.setSource(mSearchable.getSearchActivity());
+ mSourceSelector.setAppSearchData(mAppSearchData);
+
// In Donut, we special-case the case of the browser to hide the app icon as if it were
// global search, for extra space for url entry.
//
// TODO: Remove this special case once the issue has been reconciled in Eclair.
if (mGlobalSearchMode || isBrowserSearch()) {
- mAppIcon.setImageResource(0);
- mAppIcon.setVisibility(View.GONE);
+ mSourceSelector.setSourceIcon(null);
+ mSourceSelector.setVisibility(View.GONE);
mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_GLOBAL,
mSearchPlate.getPaddingTop(),
mSearchPlate.getPaddingRight(),
@@ -628,8 +631,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
icon = pm.getDefaultActivityIcon();
Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
}
- mAppIcon.setImageDrawable(icon);
- mAppIcon.setVisibility(View.VISIBLE);
+ mSourceSelector.setSourceIcon(icon);
+ mSourceSelector.setVisibility(View.VISIBLE);
mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL,
mSearchPlate.getPaddingTop(),
mSearchPlate.getPaddingRight(),
@@ -812,6 +815,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (!mSearchAutoComplete.isPerformingCompletion()) {
// The user changed the query, remember it.
mUserQuery = s == null ? "" : s.toString();
+ mSourceSelector.setQuery(mUserQuery);
}
}
@@ -823,7 +827,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// The user changed the query, check if it is a URL and if so change the search
// button in the soft keyboard to the 'Go' button.
int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION));
- if (Regex.WEB_URL_PATTERN.matcher(mUserQuery).matches()) {
+ if (Patterns.WEB_URL.matcher(mUserQuery).matches()) {
options = options | EditorInfo.IME_ACTION_GO;
} else {
options = options | EditorInfo.IME_ACTION_SEARCH;
@@ -1927,6 +1931,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
query = "";
}
mUserQuery = query;
+ mSourceSelector.setQuery(query);
mSearchAutoComplete.setText(query);
mSearchAutoComplete.setSelection(query.length());
}
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 2e94a2f..a75e8dc 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -16,17 +16,22 @@
package android.app;
+import android.Manifest;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.server.search.SearchableInfo;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -1327,6 +1332,22 @@ public class SearchManager
public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
/**
+ * String extra data key for {@link Intent#ACTION_GLOBAL_SEARCH} intents. Contains the initial
+ * query to show in the global search activity.
+ *
+ * @hide Pending API council approval
+ */
+ public final static String INITIAL_QUERY = "initial_query";
+
+ /**
+ * Boolean extra data key for {@link Intent#ACTION_GLOBAL_SEARCH} intents. If {@code true},
+ * the initial query should be selected.
+ *
+ * @hide Pending API council approval
+ */
+ public final static String SELECT_INITIAL_QUERY = "select_initial_query";
+
+ /**
* Defines the constants used in the communication between {@link android.app.SearchDialog} and
* the global search provider via {@link Cursor#respond(android.os.Bundle)}.
*
@@ -1592,6 +1613,15 @@ public class SearchManager
public final static String SUGGEST_PARAMETER_LIMIT = "limit";
/**
+ * Intent action for opening the search source selection activity.
+ * The intent may include these extra values:
+ * {@link #QUERY},
+ * {@link #APP_DATA}.
+ */
+ public static final String INTENT_ACTION_SELECT_SEARCH_SOURCE
+ = "android.intent.action.SELECT_SEARCH_SOURCE";
+
+ /**
* If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
* the search dialog will switch to a different suggestion source when the
* suggestion is clicked.
@@ -1757,7 +1787,13 @@ public class SearchManager
boolean globalSearch) {
if (mIdent == 0) throw new IllegalArgumentException(
"Called from outside of an Activity context");
- if (!globalSearch && !mAssociatedPackage.equals(launchActivity.getPackageName())) {
+
+ if (globalSearch) {
+ startGlobalSearch(initialQuery, selectInitialQuery, appSearchData);
+ return;
+ }
+
+ if (!mAssociatedPackage.equals(launchActivity.getPackageName())) {
Log.w(TAG, "invoking app search on a different package " +
"not associated with this search manager");
}
@@ -1771,6 +1807,65 @@ public class SearchManager
}
/**
+ * Starts the global search activity.
+ */
+ private void startGlobalSearch(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData) {
+ ComponentName globalSearchActivity = getGlobalSearchActivity();
+ if (globalSearchActivity == null) {
+ Log.w(TAG, "No global search activity found.");
+ return;
+ }
+ Intent intent = new Intent(Intent.ACTION_GLOBAL_SEARCH);
+ intent.setComponent(globalSearchActivity);
+ // TODO: Always pass name of calling package as an extra?
+ if (appSearchData != null) {
+ intent.putExtra(APP_DATA, appSearchData);
+ }
+ if (!TextUtils.isEmpty(initialQuery)) {
+ intent.putExtra(INITIAL_QUERY, initialQuery);
+ }
+ if (selectInitialQuery) {
+ intent.putExtra(SELECT_INITIAL_QUERY, selectInitialQuery);
+ }
+ try {
+ if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0));
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
+ }
+ }
+
+ /**
+ * Gets the name of the global search activity.
+ *
+ * This is currently implemented by returning the first activity that handles
+ * the GLOBAL_SEARCH intent and has the GLOBAL_SEARCH permission. If we allow
+ * more than one global search acitivity to be installed, this code must be changed.
+ *
+ * TODO: Doing this every time we start global search is inefficient. Will fix that once
+ * we have settled on the right mechanism for finding the global search activity.
+ */
+ private ComponentName getGlobalSearchActivity() {
+ Intent intent = new Intent(Intent.ACTION_GLOBAL_SEARCH);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> activities =
+ pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ int count = activities.size();
+ for (int i = 0; i < count; i++) {
+ ActivityInfo ai = activities.get(i).activityInfo;
+ if (pm.checkPermission(Manifest.permission.GLOBAL_SEARCH,
+ ai.packageName) == PackageManager.PERMISSION_GRANTED) {
+ return new ComponentName(ai.packageName, ai.name);
+ } else {
+ Log.w(TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
+ + "but does not have the GLOBAL_SEARCH permission.");
+ }
+ }
+ return null;
+ }
+
+ /**
* Similar to {@link #startSearch} but actually fires off the search query after invoking
* the search dialog. Made available for testing purposes.
*
@@ -1935,8 +2030,23 @@ public class SearchManager
}
/**
- * Gets information about a searchable activity. This method is static so that it can
- * be used from non-Activity contexts.
+ * Gets information about a searchable activity.
+ *
+ * @param componentName The activity to get searchable information for.
+ * @return Searchable information, or <code>null</code> if the activity does not
+ * exist, or is not searchable.
+ */
+ public SearchableInfo getSearchableInfo(ComponentName componentName) {
+ try {
+ return mService.getSearchableInfo(componentName, false);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getSearchableInfo() failed: " + ex);
+ return null;
+ }
+ }
+
+ /**
+ * Gets information about a searchable activity.
*
* @param componentName The activity to get searchable information for.
* @param globalSearch If <code>false</code>, return information about the given activity.
@@ -2039,10 +2149,8 @@ public class SearchManager
* Returns a list of the searchable activities that can be included in global search.
*
* @return a list containing searchable information for all searchable activities
- * that have the <code>exported</code> attribute set in their searchable
- * meta-data.
- *
- * @hide because SearchableInfo is not part of the API.
+ * that have the <code>android:includeInGlobalSearch</code> attribute set
+ * in their searchable meta-data.
*/
public List<SearchableInfo> getSearchablesInGlobalSearch() {
try {
diff --git a/core/java/android/app/SearchSourceSelector.java b/core/java/android/app/SearchSourceSelector.java
new file mode 100644
index 0000000..fabf858
--- /dev/null
+++ b/core/java/android/app/SearchSourceSelector.java
@@ -0,0 +1,197 @@
+/*
+ * 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 android.app;
+
+import com.android.internal.R;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageButton;
+
+import java.util.List;
+
+/**
+ * Utilities for setting up the search source selector.
+ *
+ * This class has two copies:
+ * android.app.SearchSourceSelector
+ * com.android.quicksearchbox.ui.SearchSourceSelector
+ *
+ * They should keep the same look and feel as much as possible,
+ * but only the intent details must absolutely stay in sync.
+ *
+ * @hide
+ */
+public class SearchSourceSelector implements View.OnClickListener {
+
+ private static final String TAG = "SearchSourceSelector";
+
+ // TODO: This should be defined in android.provider.Applications,
+ // and have a less made-up value.
+ private static final String APPLICATION_TYPE = "application/vnd.android.application";
+
+ public static final int ICON_VIEW_ID = R.id.search_source_selector_icon;
+
+ private final View mView;
+
+ private final ImageButton mIconView;
+
+ private ComponentName mSource;
+
+ private Bundle mAppSearchData;
+
+ private String mQuery;
+
+ public SearchSourceSelector(View view) {
+ mView = view;
+ mIconView = (ImageButton) view.findViewById(ICON_VIEW_ID);
+ mIconView.setOnClickListener(this);
+ }
+
+ /**
+ * Sets the icon displayed in the search source selector.
+ */
+ public void setSourceIcon(Drawable icon) {
+ mIconView.setImageDrawable(icon);
+ }
+
+ /**
+ * Sets the current search source.
+ */
+ public void setSource(ComponentName source) {
+ mSource = source;
+ }
+
+ /**
+ * Sets the app-specific data that will be passed to the search activity if
+ * the user opens the source selector and chooses a source.
+ */
+ public void setAppSearchData(Bundle appSearchData) {
+ mAppSearchData = appSearchData;
+ }
+
+ /**
+ * Sets the initial query that will be passed to the search activity if
+ * the user opens the source selector and chooses a source.
+ */
+ public void setQuery(String query) {
+ mQuery = query;
+ }
+
+ public void setVisibility(int visibility) {
+ mView.setVisibility(visibility);
+ }
+
+ /**
+ * Creates an intent for opening the search source selector activity.
+ *
+ * @param source The current search source.
+ * @param query The initial query that will be passed to the search activity if
+ * the user opens the source selector and chooses a source.
+ * @param appSearchData The app-specific data that will be passed to the search
+ * activity if the user opens the source selector and chooses a source.
+ */
+ public static Intent createIntent(ComponentName source, String query, Bundle appSearchData) {
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_SELECT_SEARCH_SOURCE);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ Uri sourceUri = componentNameToUri(source);
+ if (sourceUri != null) {
+ intent.setDataAndType(sourceUri, APPLICATION_TYPE);
+ }
+ if (query != null) {
+ intent.putExtra(SearchManager.QUERY, query);
+ }
+ if (query != null) {
+ intent.putExtra(SearchManager.APP_DATA, appSearchData);
+ }
+ return intent;
+ }
+
+ /**
+ * Gets the search source from which the given
+ * {@link SearchManager.INTENT_ACTION_SELECT_SEARCH_SOURCE} intent was sent.
+ */
+ public static ComponentName getSource(Intent intent) {
+ return uriToComponentName(intent.getData());
+ }
+
+ private static Uri componentNameToUri(ComponentName name) {
+ if (name == null) return null;
+ // TODO: This URI format is specificed in android.provider.Applications which is @hidden
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("applications")
+ .appendEncodedPath("applications")
+ .appendPath(name.getPackageName())
+ .appendPath(name.getClassName())
+ .build();
+ }
+
+ private static ComponentName uriToComponentName(Uri uri) {
+ if (uri == null) return null;
+ List<String> path = uri.getPathSegments();
+ if (path == null || path.size() != 3) return null;
+ String pkg = path.get(1);
+ String cls = path.get(2);
+ if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(cls)) return null;
+ return new ComponentName(pkg, cls);
+ }
+
+ public void onClick(View v) {
+ trigger();
+ }
+
+ private void trigger() {
+ try {
+ Intent intent = createIntent(mSource, mQuery, mAppSearchData);
+ intent.setSourceBounds(getOnScreenRect(mIconView));
+ mIconView.getContext().startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "No source selector activity found", ex);
+ }
+ }
+
+ // TODO: This code is replicated in lots of places:
+ // - android.provider.ContactsContract.QuickContact.showQuickContact()
+ // - android.widget.RemoteViews.setOnClickPendingIntent()
+ // - com.android.launcher2.Launcher.onClick()
+ // - com.android.launcher.Launcher.onClick()
+ // - com.android.server.status.StatusBarService.Launcher.onClick()
+ private static Rect getOnScreenRect(View v) {
+ final float appScale = v.getResources().getCompatibilityInfo().applicationScale;
+ final int[] pos = new int[2];
+ v.getLocationOnScreen(pos);
+ final Rect rect = new Rect();
+ rect.left = (int) (pos[0] * appScale + 0.5f);
+ rect.top = (int) (pos[1] * appScale + 0.5f);
+ rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
+ rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
+ return rect;
+ }
+
+}
diff --git a/core/java/android/server/search/SearchableInfo.aidl b/core/java/android/app/SearchableInfo.aidl
index 9576c2b..146b373 100644
--- a/core/java/android/server/search/SearchableInfo.aidl
+++ b/core/java/android/app/SearchableInfo.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.server.search;
+package android.app;
parcelable SearchableInfo;
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index 69ef98c..9897742 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.server.search;
+package android.app;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -37,6 +37,11 @@ import android.view.inputmethod.EditorInfo;
import java.io.IOException;
import java.util.HashMap;
+/**
+ * Searchability meta-data for an activity.
+ * See <a href="SearchManager.html#SearchabilityMetadata">Searchability meta-data</a>
+ * for more information.
+ */
public final class SearchableInfo implements Parcelable {
// general debugging support
@@ -83,9 +88,9 @@ public final class SearchableInfo implements Parcelable {
private final String mSuggestProviderPackage;
// Flag values for Searchable_voiceSearchMode
- private static int VOICE_SEARCH_SHOW_BUTTON = 1;
- private static int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
- private static int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
+ private static final int VOICE_SEARCH_SHOW_BUTTON = 1;
+ private static final int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
+ private static final int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
private final int mVoiceSearchMode;
private final int mVoiceLanguageModeId; // voiceLanguageModel
private final int mVoicePromptTextId; // voicePromptText
@@ -119,6 +124,8 @@ public final class SearchableInfo implements Parcelable {
/**
* Checks whether the badge should be a text label.
+ *
+ * @hide This feature is deprecated, no need to add it to the API.
*/
public boolean useBadgeLabel() {
return 0 != (mSearchMode & SEARCH_MODE_BADGE_LABEL);
@@ -126,6 +133,8 @@ public final class SearchableInfo implements Parcelable {
/**
* Checks whether the badge should be an icon.
+ *
+ * @hide This feature is deprecated, no need to add it to the API.
*/
public boolean useBadgeIcon() {
return (0 != (mSearchMode & SEARCH_MODE_BADGE_ICON)) && (mIconId != 0);
@@ -216,6 +225,7 @@ public final class SearchableInfo implements Parcelable {
*
* @param context You need to supply a context to start with
* @return Returns a context related to the searchable activity
+ * @hide
*/
public Context getActivityContext(Context context) {
return createActivityContext(context, mSearchActivity);
@@ -247,6 +257,7 @@ public final class SearchableInfo implements Parcelable {
* @param activityContext If we can determine that the provider and the activity are the
* same, we'll just return this one.
* @return Returns a context related to the context provider
+ * @hide
*/
public Context getProviderContext(Context context, Context activityContext) {
Context theirContext = null;
@@ -347,7 +358,11 @@ public final class SearchableInfo implements Parcelable {
}
/**
- * Private class used to hold the "action key" configuration
+ * Information about an action key in searchability meta-data.
+ * See <a href="SearchManager.html#SearchabilityMetadata">Searchability meta-data</a>
+ * for more information.
+ *
+ * @see SearchableInfo#findActionKey(int)
*/
public static class ActionKeyInfo implements Parcelable {
@@ -364,7 +379,7 @@ public final class SearchableInfo implements Parcelable {
* construct the object.
* @throws IllegalArgumentException if the action key configuration is invalid
*/
- public ActionKeyInfo(Context activityContext, AttributeSet attr) {
+ ActionKeyInfo(Context activityContext, AttributeSet attr) {
TypedArray a = activityContext.obtainStyledAttributes(attr,
com.android.internal.R.styleable.SearchableActionKey);
@@ -395,7 +410,7 @@ public final class SearchableInfo implements Parcelable {
* @param in The Parcel containing the previously written ActionKeyInfo,
* positioned at the location in the buffer where it was written.
*/
- public ActionKeyInfo(Parcel in) {
+ private ActionKeyInfo(Parcel in) {
mKeyCode = in.readInt();
mQueryActionMsg = in.readString();
mSuggestActionMsg = in.readString();
@@ -457,6 +472,8 @@ public final class SearchableInfo implements Parcelable {
* @param activityInfo Activity to get search information from.
* @return Search information about the given activity, or {@code null} if
* the activity has no or invalid searchability meta-data.
+ *
+ * @hide For use by SearchManagerService.
*/
public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo) {
// for each component, try to find metadata
@@ -716,7 +733,7 @@ public final class SearchableInfo implements Parcelable {
* @param in The Parcel containing the previously written SearchableInfo,
* positioned at the location in the buffer where it was written.
*/
- public SearchableInfo(Parcel in) {
+ SearchableInfo(Parcel in) {
mLabelId = in.readInt();
mSearchActivity = ComponentName.readFromParcel(in);
mHintId = in.readInt();
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 30e1712..c4e1877 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -287,6 +287,14 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* {@link #onStart} and returns either {@link #START_STICKY}
* or {@link #START_STICKY_COMPATIBILITY}.
*
+ * <p>If you need your application to run on platform versions prior to API
+ * level 5, you can use the following model to handle the older {@link #onStart}
+ * callback in that case. The <code>handleCommand</code> method is implemented by
+ * you as appropriate:
+ *
+ * <pre>{@include development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
+ * start_compatibility}</pre>
+ *
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
@@ -462,6 +470,13 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* if your service is performing background music playback, so the user
* would notice if their music stopped playing.
*
+ * <p>If you need your application to run on platform versions prior to API
+ * level 5, you can use the following model to call the the older {@link #setForeground}
+ * or this modern method as appropriate:
+ *
+ * <pre>{@include development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
+ * foreground_compatibility}</pre>
+ *
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
* NotificationManager.notify(int, Notification)}.
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 12be97c..173c3e1 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -31,7 +31,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.net.Uri;
import android.os.Bundle;
-import android.server.search.SearchableInfo;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 2f719f3..792b289 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -311,8 +311,8 @@ public class AppWidgetHostView extends FrameLayout {
// Take requested dimensions from child, but apply default gravity.
FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
if (requested == null) {
- requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT,
- LayoutParams.FILL_PARENT);
+ requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
}
requested.gravity = Gravity.CENTER;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8eda844..42d87f4 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -63,7 +63,7 @@ import java.util.UUID;
*/
public final class BluetoothAdapter {
private static final String TAG = "BluetoothAdapter";
- private static final boolean DBG = false;
+ private static final boolean DBG = true; //STOPSHIP: Remove excess logging
/**
* Sentinel error value for this class. Guaranteed to not equal any other
diff --git a/core/java/android/content/AbstractCursorEntityIterator.java b/core/java/android/content/AbstractCursorEntityIterator.java
deleted file mode 100644
index a804f3c..0000000
--- a/core/java/android/content/AbstractCursorEntityIterator.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package android.content;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.RemoteException;
-
-/**
- * An abstract class that makes it easy to implement an EntityIterator over a cursor.
- * The user must implement {@link #newEntityFromCursorLocked}, which runs inside of a
- * database transaction.
- * @hide
- */
-public abstract class AbstractCursorEntityIterator implements EntityIterator {
- private final Cursor mEntityCursor;
- private final SQLiteDatabase mDb;
- private volatile Entity mNextEntity;
- private volatile boolean mIsClosed;
-
- public AbstractCursorEntityIterator(SQLiteDatabase db, Cursor entityCursor) {
- mEntityCursor = entityCursor;
- mDb = db;
- mNextEntity = null;
- mIsClosed = false;
- }
-
- /**
- * If there are entries left in the cursor then advance the cursor and use the new row to
- * populate mNextEntity. If the cursor is at the end or if advancing it causes the cursor
- * to become at the end then set mEntityCursor to null. If newEntityFromCursor returns null
- * then continue advancing until it either returns a non-null Entity or the cursor reaches
- * the end.
- */
- private void fillEntityIfAvailable() {
- while (mNextEntity == null) {
- if (!mEntityCursor.moveToNext()) {
- // the cursor is at then end, bail out
- return;
- }
- // This may return null if newEntityFromCursor is not able to create an entity
- // from the current cursor position. In that case this method will loop and try
- // the next cursor position
- mNextEntity = newEntityFromCursorLocked(mEntityCursor);
- }
- mDb.beginTransaction();
- try {
- int position = mEntityCursor.getPosition();
- mNextEntity = newEntityFromCursorLocked(mEntityCursor);
- int newPosition = mEntityCursor.getPosition();
- if (newPosition != position) {
- throw new IllegalStateException("the cursor position changed during the call to"
- + "newEntityFromCursorLocked, from " + position + " to " + newPosition);
- }
- } finally {
- mDb.endTransaction();
- }
- }
-
- /**
- * Checks if there are more Entities accessible via this iterator. This may not be called
- * if the iterator is already closed.
- * @return true if the call to next() will return an Entity.
- */
- public boolean hasNext() {
- if (mIsClosed) {
- throw new IllegalStateException("calling hasNext() when the iterator is closed");
- }
- fillEntityIfAvailable();
- return mNextEntity != null;
- }
-
- /**
- * Returns the next Entity that is accessible via this iterator. This may not be called
- * if the iterator is already closed.
- * @return the next Entity that is accessible via this iterator
- */
- public Entity next() {
- if (mIsClosed) {
- throw new IllegalStateException("calling next() when the iterator is closed");
- }
- if (!hasNext()) {
- throw new IllegalStateException("you may only call next() if hasNext() is true");
- }
-
- try {
- return mNextEntity;
- } finally {
- mNextEntity = null;
- }
- }
-
- public void reset() throws RemoteException {
- if (mIsClosed) {
- throw new IllegalStateException("calling reset() when the iterator is closed");
- }
- mEntityCursor.moveToPosition(-1);
- mNextEntity = null;
- }
-
- /**
- * Closes this iterator making it invalid. If is invalid for the user to call any public
- * method on the iterator once it has been closed.
- */
- public void close() {
- if (mIsClosed) {
- throw new IllegalStateException("closing when already closed");
- }
- mIsClosed = true;
- mEntityCursor.close();
- }
-
- /**
- * Returns a new Entity from the current cursor position. This is called from within a
- * database transaction. If a new entity cannot be created from this cursor position (e.g.
- * if the row that is referred to no longer exists) then this may return null. The cursor
- * is guaranteed to be pointing to a valid row when this call is made. The implementation
- * of newEntityFromCursorLocked is not allowed to change the position of the cursor.
- * @param cursor from where to read the data for the Entity
- * @return an Entity that corresponds to the current cursor position or null
- */
- public abstract Entity newEntityFromCursorLocked(Cursor cursor);
-}
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
deleted file mode 100644
index dd89097..0000000
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ /dev/null
@@ -1,755 +0,0 @@
-package android.content;
-
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.Cursor;
-import android.net.Uri;
-import android.accounts.OnAccountsUpdateListener;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.provider.SyncConstValue;
-import android.util.Config;
-import android.util.Log;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Vector;
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.HashSet;
-
-import com.google.android.collect.Maps;
-
-/**
- * A specialization of the ContentProvider that centralizes functionality
- * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
- * inside of database transactions.
- *
- * @hide
- */
-public abstract class AbstractSyncableContentProvider extends SyncableContentProvider {
- private static final String TAG = "SyncableContentProvider";
- protected SQLiteOpenHelper mOpenHelper;
- protected SQLiteDatabase mDb;
- private final String mDatabaseName;
- private final int mDatabaseVersion;
- private final Uri mContentUri;
-
- /** the account set in the last call to onSyncStart() */
- private Account mSyncingAccount;
-
- private SyncStateContentProviderHelper mSyncState = null;
-
- private static final String[] sAccountProjection =
- new String[] {SyncConstValue._SYNC_ACCOUNT, SyncConstValue._SYNC_ACCOUNT_TYPE};
-
- private boolean mIsTemporary;
-
- private AbstractTableMerger mCurrentMerger = null;
- private boolean mIsMergeCancelled = false;
-
- private static final String SYNC_ACCOUNT_WHERE_CLAUSE =
- SyncConstValue._SYNC_ACCOUNT + "=? AND " + SyncConstValue._SYNC_ACCOUNT_TYPE + "=?";
-
- protected boolean isTemporary() {
- return mIsTemporary;
- }
-
- private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
- private final ThreadLocal<Set<Uri>> mPendingBatchNotifications = new ThreadLocal<Set<Uri>>();
-
- /**
- * Indicates whether or not this ContentProvider contains a full
- * set of data or just diffs. This knowledge comes in handy when
- * determining how to incorporate the contents of a temporary
- * provider into a real provider.
- */
- private boolean mContainsDiffs;
-
- /**
- * Initializes the AbstractSyncableContentProvider
- * @param dbName the filename of the database
- * @param dbVersion the current version of the database schema
- * @param contentUri The base Uri of the syncable content in this provider
- */
- public AbstractSyncableContentProvider(String dbName, int dbVersion, Uri contentUri) {
- super();
-
- mDatabaseName = dbName;
- mDatabaseVersion = dbVersion;
- mContentUri = contentUri;
- mIsTemporary = false;
- setContainsDiffs(false);
- if (Config.LOGV) {
- Log.v(TAG, "created SyncableContentProvider " + this);
- }
- }
-
- /**
- * Close resources that must be closed. You must call this to properly release
- * the resources used by the AbstractSyncableContentProvider.
- */
- public void close() {
- if (mOpenHelper != null) {
- mOpenHelper.close(); // OK to call .close() repeatedly.
- }
- }
-
- /**
- * Override to create your schema and do anything else you need to do with a new database.
- * This is run inside a transaction (so you don't need to use one).
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- */
- protected void bootstrapDatabase(SQLiteDatabase db) {}
-
- /**
- * Override to upgrade your database from an old version to the version you specified.
- * Don't set the DB version; this will automatically be done after the method returns.
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- *
- * @param oldVersion version of the existing database
- * @param newVersion current version to upgrade to
- * @return true if the upgrade was lossless, false if it was lossy
- */
- protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
-
- /**
- * Override to do anything (like cleanups or checks) you need to do after opening a database.
- * Does nothing by default. This is run inside a transaction (so you don't need to use one).
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- */
- protected void onDatabaseOpened(SQLiteDatabase db) {}
-
- private class DatabaseHelper extends SQLiteOpenHelper {
- DatabaseHelper(Context context, String name) {
- // Note: context and name may be null for temp providers
- super(context, name, null, mDatabaseVersion);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- bootstrapDatabase(db);
- mSyncState.createDatabase(db);
- if (!isTemporary()) {
- ContentResolver.requestSync(null /* all accounts */,
- mContentUri.getAuthority(), new Bundle());
- }
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (!upgradeDatabase(db, oldVersion, newVersion)) {
- mSyncState.discardSyncData(db, null /* all accounts */);
- ContentResolver.requestSync(null /* all accounts */,
- mContentUri.getAuthority(), new Bundle());
- }
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- onDatabaseOpened(db);
- mSyncState.onDatabaseOpened(db);
- }
- }
-
- @Override
- public boolean onCreate() {
- if (isTemporary()) throw new IllegalStateException("onCreate() called for temp provider");
- mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(),
- mDatabaseName);
- mSyncState = new SyncStateContentProviderHelper(mOpenHelper);
- AccountManager.get(getContext()).addOnAccountsUpdatedListener(
- new OnAccountsUpdateListener() {
- public void onAccountsUpdated(Account[] accounts) {
- // Some providers override onAccountsChanged(); give them a database to
- // work with.
- mDb = mOpenHelper.getWritableDatabase();
- // Only call onAccountsChanged on GAIA accounts; otherwise, the contacts and
- // calendar providers will choke as they try to sync unknown accounts with
- // AbstractGDataSyncAdapter, which will put acore into a crash loop
- ArrayList<Account> gaiaAccounts = new ArrayList<Account>();
- for (Account acct: accounts) {
- if (acct.type.equals("com.google")) {
- gaiaAccounts.add(acct);
- }
- }
- accounts = new Account[gaiaAccounts.size()];
- int i = 0;
- for (Account acct: gaiaAccounts) {
- accounts[i++] = acct;
- }
- onAccountsChanged(accounts);
- TempProviderSyncAdapter syncAdapter = getTempProviderSyncAdapter();
- if (syncAdapter != null) {
- syncAdapter.onAccountsChanged(accounts);
- }
- }
- }, null /* handler */, true /* updateImmediately */);
-
- return true;
- }
- /**
- * Get a non-persistent instance of this content provider.
- * You must call {@link #close} on the returned
- * SyncableContentProvider when you are done with it.
- *
- * @return a non-persistent content provider with the same layout as this
- * provider.
- */
- public AbstractSyncableContentProvider getTemporaryInstance() {
- AbstractSyncableContentProvider temp;
- try {
- temp = getClass().newInstance();
- } catch (InstantiationException e) {
- throw new RuntimeException("unable to instantiate class, "
- + "this should never happen", e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "IllegalAccess while instantiating class, "
- + "this should never happen", e);
- }
-
- // Note: onCreate() isn't run for the temp provider, and it has no Context.
- temp.mIsTemporary = true;
- temp.setContainsDiffs(true);
- temp.mOpenHelper = temp.new DatabaseHelper(null, null);
- temp.mSyncState = new SyncStateContentProviderHelper(temp.mOpenHelper);
- if (!isTemporary()) {
- mSyncState.copySyncState(
- mOpenHelper.getReadableDatabase(),
- temp.mOpenHelper.getWritableDatabase(),
- getSyncingAccount());
- }
- return temp;
- }
-
- public SQLiteDatabase getDatabase() {
- if (mDb == null) mDb = mOpenHelper.getWritableDatabase();
- return mDb;
- }
-
- public boolean getContainsDiffs() {
- return mContainsDiffs;
- }
-
- public void setContainsDiffs(boolean containsDiffs) {
- if (containsDiffs && !isTemporary()) {
- throw new IllegalStateException(
- "only a temporary provider can contain diffs");
- }
- mContainsDiffs = containsDiffs;
- }
-
- /**
- * Each subclass of this class should define a subclass of {@link
- * android.content.AbstractTableMerger} for each table they wish to merge. It
- * should then override this method and return one instance of
- * each merger, in sequence. Their {@link
- * android.content.AbstractTableMerger#merge merge} methods will be called, one at a
- * time, in the order supplied.
- *
- * <p>The default implementation returns an empty list, so that no
- * merging will occur.
- * @return A sequence of subclasses of {@link
- * android.content.AbstractTableMerger}, one for each table that should be merged.
- */
- protected Iterable<? extends AbstractTableMerger> getMergers() {
- return Collections.emptyList();
- }
-
- @Override
- public final int update(final Uri url, final ContentValues values,
- final String selection, final String[] selectionArgs) {
- mDb = mOpenHelper.getWritableDatabase();
- final boolean notApplyingBatch = !applyingBatch();
- if (notApplyingBatch) {
- mDb.beginTransaction();
- }
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().update(
- url, values, selection, selectionArgs);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- return numRows;
- }
-
- int result = updateInternal(url, values, selection, selectionArgs);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- if (!isTemporary() && result > 0) {
- if (notApplyingBatch) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- } else {
- mPendingBatchNotifications.get().add(url);
- }
- }
- return result;
- } finally {
- if (notApplyingBatch) {
- mDb.endTransaction();
- }
- }
- }
-
- @Override
- public final int delete(final Uri url, final String selection,
- final String[] selectionArgs) {
- mDb = mOpenHelper.getWritableDatabase();
- final boolean notApplyingBatch = !applyingBatch();
- if (notApplyingBatch) {
- mDb.beginTransaction();
- }
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- return numRows;
- }
- int result = deleteInternal(url, selection, selectionArgs);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- if (!isTemporary() && result > 0) {
- if (notApplyingBatch) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- } else {
- mPendingBatchNotifications.get().add(url);
- }
- }
- return result;
- } finally {
- if (notApplyingBatch) {
- mDb.endTransaction();
- }
- }
- }
-
- private boolean applyingBatch() {
- return mApplyingBatch.get() != null && mApplyingBatch.get();
- }
-
- @Override
- public final Uri insert(final Uri url, final ContentValues values) {
- mDb = mOpenHelper.getWritableDatabase();
- final boolean notApplyingBatch = !applyingBatch();
- if (notApplyingBatch) {
- mDb.beginTransaction();
- }
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- Uri result = mSyncState.asContentProvider().insert(url, values);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- return result;
- }
- Uri result = insertInternal(url, values);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- if (!isTemporary() && result != null) {
- if (notApplyingBatch) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- } else {
- mPendingBatchNotifications.get().add(url);
- }
- }
- return result;
- } finally {
- if (notApplyingBatch) {
- mDb.endTransaction();
- }
- }
- }
-
- @Override
- public final int bulkInsert(final Uri uri, final ContentValues[] values) {
- int size = values.length;
- int completed = 0;
- final boolean isSyncStateUri = mSyncState.matches(uri);
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
- try {
- for (int i = 0; i < size; i++) {
- Uri result;
- if (isTemporary() && isSyncStateUri) {
- result = mSyncState.asContentProvider().insert(uri, values[i]);
- } else {
- result = insertInternal(uri, values[i]);
- mDb.yieldIfContended();
- }
- if (result != null) {
- completed++;
- }
- }
- mDb.setTransactionSuccessful();
- } finally {
- mDb.endTransaction();
- }
- if (!isTemporary() && completed == size) {
- getContext().getContentResolver().notifyChange(uri, null /* observer */,
- changeRequiresLocalSync(uri));
- }
- return completed;
- }
-
- /**
- * <p>
- * Start batch transaction. {@link #endTransaction} MUST be called after
- * calling this method. Those methods should be used like this:
- * </p>
- *
- * <pre class="prettyprint">
- * boolean successful = false;
- * beginBatch()
- * try {
- * // Do something related to mDb
- * successful = true;
- * return ret;
- * } finally {
- * endBatch(successful);
- * }
- * </pre>
- *
- * @hide This method should be used only when {@link ContentProvider#applyBatch} is not enough and must be
- * used with {@link #endBatch}.
- * e.g. If returned value has to be used during one transaction, this method might be useful.
- */
- public final void beginBatch() {
- // initialize if this is the first time this thread has applied a batch
- if (mApplyingBatch.get() == null) {
- mApplyingBatch.set(false);
- mPendingBatchNotifications.set(new HashSet<Uri>());
- }
-
- if (applyingBatch()) {
- throw new IllegalStateException(
- "applyBatch is not reentrant but mApplyingBatch is already set");
- }
- SQLiteDatabase db = getDatabase();
- db.beginTransaction();
- boolean successful = false;
- try {
- mApplyingBatch.set(true);
- successful = true;
- } finally {
- if (!successful) {
- // Something unexpected happened. We must call endTransaction() at least.
- db.endTransaction();
- }
- }
- }
-
- /**
- * <p>
- * Finish batch transaction. If "successful" is true, try to call
- * mDb.setTransactionSuccessful() before calling mDb.endTransaction().
- * This method MUST be used with {@link #beginBatch()}.
- * </p>
- *
- * @hide This method must be used with {@link #beginTransaction}
- */
- public final void endBatch(boolean successful) {
- try {
- if (successful) {
- // setTransactionSuccessful() must be called just once during opening the
- // transaction.
- mDb.setTransactionSuccessful();
- }
- } finally {
- mApplyingBatch.set(false);
- getDatabase().endTransaction();
- for (Uri url : mPendingBatchNotifications.get()) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
- }
- }
-
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
- throws OperationApplicationException {
- boolean successful = false;
- beginBatch();
- try {
- ContentProviderResult[] results = super.applyBatch(operations);
- successful = true;
- return results;
- } finally {
- endBatch(successful);
- }
- }
-
- /**
- * Check if changes to this URI can be syncable changes.
- * @param uri the URI of the resource that was changed
- * @return true if changes to this URI can be syncable changes, false otherwise
- */
- public boolean changeRequiresLocalSync(Uri uri) {
- return true;
- }
-
- @Override
- public final Cursor query(final Uri url, final String[] projection,
- final String selection, final String[] selectionArgs,
- final String sortOrder) {
- mDb = mOpenHelper.getReadableDatabase();
- if (isTemporary() && mSyncState.matches(url)) {
- return mSyncState.asContentProvider().query(
- url, projection, selection, selectionArgs, sortOrder);
- }
- return queryInternal(url, projection, selection, selectionArgs, sortOrder);
- }
-
- /**
- * Called right before a sync is started.
- *
- * @param context the sync context for the operation
- * @param account
- */
- public void onSyncStart(SyncContext context, Account account) {
- if (account == null) {
- throw new IllegalArgumentException("you passed in an empty account");
- }
- mSyncingAccount = account;
- }
-
- /**
- * Called right after a sync is completed
- *
- * @param context the sync context for the operation
- * @param success true if the sync succeeded, false if an error occurred
- */
- public void onSyncStop(SyncContext context, boolean success) {
- }
-
- /**
- * The account of the most recent call to onSyncStart()
- * @return the account
- */
- public Account getSyncingAccount() {
- return mSyncingAccount;
- }
-
- /**
- * Merge diffs from a sync source with this content provider.
- *
- * @param context the SyncContext within which this merge is taking place
- * @param diffs A temporary content provider containing diffs from a sync
- * source.
- * @param result a MergeResult that contains information about the merge, including
- * a temporary content provider with the same layout as this provider containing
- * @param syncResult
- */
- public void merge(SyncContext context, SyncableContentProvider diffs,
- TempProviderSyncResult result, SyncResult syncResult) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- synchronized(this) {
- mIsMergeCancelled = false;
- }
- Iterable<? extends AbstractTableMerger> mergers = getMergers();
- try {
- for (AbstractTableMerger merger : mergers) {
- synchronized(this) {
- if (mIsMergeCancelled) break;
- mCurrentMerger = merger;
- }
- merger.merge(context, getSyncingAccount(), diffs, result, syncResult, this);
- }
- if (mIsMergeCancelled) return;
- if (diffs != null) {
- mSyncState.copySyncState(
- ((AbstractSyncableContentProvider)diffs).mOpenHelper.getReadableDatabase(),
- mOpenHelper.getWritableDatabase(),
- getSyncingAccount());
- }
- } finally {
- synchronized (this) {
- mCurrentMerger = null;
- }
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
-
- /**
- * Invoked when the active sync has been canceled. Sets the sync state of this provider and
- * its merger to canceled.
- */
- public void onSyncCanceled() {
- synchronized (this) {
- mIsMergeCancelled = true;
- if (mCurrentMerger != null) {
- mCurrentMerger.onMergeCancelled();
- }
- }
- }
-
-
- public boolean isMergeCancelled() {
- return mIsMergeCancelled;
- }
-
- /**
- * Subclasses should override this instead of update(). See update()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract int updateInternal(Uri url, ContentValues values,
- String selection, String[] selectionArgs);
-
- /**
- * Subclasses should override this instead of delete(). See delete()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
-
- /**
- * Subclasses should override this instead of insert(). See insert()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract Uri insertInternal(Uri url, ContentValues values);
-
- /**
- * Subclasses should override this instead of query(). See query()
- * for details.
- *
- * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
- * block for performance reasons. If an implementation needs atomic access
- * to the database the lock can be acquired then.
- */
- protected abstract Cursor queryInternal(Uri url, String[] projection,
- String selection, String[] selectionArgs, String sortOrder);
-
- /**
- * Make sure that there are no entries for accounts that no longer exist
- * @param accountsArray the array of currently-existing accounts
- */
- protected void onAccountsChanged(Account[] accountsArray) {
- Map<Account, Boolean> accounts = Maps.newHashMap();
- for (Account account : accountsArray) {
- accounts.put(account, false);
- }
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Map<String, String> tableMap = db.getSyncedTables();
- Vector<String> tables = new Vector<String>();
- tables.addAll(tableMap.keySet());
- tables.addAll(tableMap.values());
-
- db.beginTransaction();
- try {
- mSyncState.onAccountsChanged(accountsArray);
- for (String table : tables) {
- deleteRowsForRemovedAccounts(accounts, table);
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- /**
- * A helper method to delete all rows whose account is not in the accounts
- * map. The accountColumnName is the name of the column that is expected
- * to hold the account. If a row has an empty account it is never deleted.
- *
- * @param accounts a map of existing accounts
- * @param table the table to delete from
- */
- protected void deleteRowsForRemovedAccounts(Map<Account, Boolean> accounts, String table) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Cursor c = db.query(table, sAccountProjection, null, null,
- "_sync_account, _sync_account_type", null, null);
- try {
- while (c.moveToNext()) {
- String accountName = c.getString(0);
- String accountType = c.getString(1);
- if ("localhost".equals(accountType)) continue;
- if (TextUtils.isEmpty(accountName)) {
- continue;
- }
- Account account = new Account(accountName, accountType);
- if (!accounts.containsKey(account)) {
- int numDeleted;
- numDeleted = db.delete(table, "_sync_account=? AND _sync_account_type=?",
- new String[]{account.name, account.type});
- if (Config.LOGV) {
- Log.v(TAG, "deleted " + numDeleted
- + " records from table " + table
- + " for account " + account);
- }
- }
- }
- } finally {
- c.close();
- }
- }
-
- /**
- * Called when the sync system determines that this provider should no longer
- * contain records for the specified account.
- */
- public void wipeAccount(Account account) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Map<String, String> tableMap = db.getSyncedTables();
- ArrayList<String> tables = new ArrayList<String>();
- tables.addAll(tableMap.keySet());
- tables.addAll(tableMap.values());
-
- db.beginTransaction();
-
- try {
- // remove the SyncState data
- mSyncState.discardSyncData(db, account);
-
- // remove the data in the synced tables
- for (String table : tables) {
- db.delete(table, SYNC_ACCOUNT_WHERE_CLAUSE,
- new String[]{account.name, account.type});
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- /**
- * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
- */
- public byte[] readSyncDataBytes(Account account) {
- return mSyncState.readSyncDataBytes(mOpenHelper.getReadableDatabase(), account);
- }
-
- /**
- * Sets the SyncData bytes for the given account. The byte array may be null.
- */
- public void writeSyncDataBytes(Account account, byte[] data) {
- mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
- }
-}
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
deleted file mode 100644
index 9545fd7..0000000
--- a/core/java/android/content/AbstractTableMerger.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * 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.content;
-
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.Debug;
-import android.provider.BaseColumns;
-import static android.provider.SyncConstValue.*;
-import android.text.TextUtils;
-import android.util.Log;
-import android.accounts.Account;
-
-/**
- * @hide
- */
-public abstract class AbstractTableMerger
-{
- private ContentValues mValues;
-
- protected SQLiteDatabase mDb;
- protected String mTable;
- protected Uri mTableURL;
- protected String mDeletedTable;
- protected Uri mDeletedTableURL;
- static protected ContentValues mSyncMarkValues;
- static private boolean TRACE;
-
- static {
- mSyncMarkValues = new ContentValues();
- mSyncMarkValues.put(_SYNC_MARK, 1);
- TRACE = false;
- }
-
- private static final String TAG = "AbstractTableMerger";
- private static final String[] syncDirtyProjection =
- new String[] {_SYNC_DIRTY, BaseColumns._ID, _SYNC_ID, _SYNC_VERSION};
- private static final String[] syncIdAndVersionProjection =
- new String[] {_SYNC_ID, _SYNC_VERSION};
-
- private volatile boolean mIsMergeCancelled;
-
- private static final String SELECT_MARKED = _SYNC_MARK + "> 0 and "
- + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?";
-
- private static final String SELECT_BY_SYNC_ID_AND_ACCOUNT =
- _SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?";
- private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
-
- private static final String SELECT_UNSYNCED =
- "(" + _SYNC_ACCOUNT + " IS NULL OR ("
- + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?)) and "
- + "(" + _SYNC_ID + " IS NULL OR (" + _SYNC_DIRTY + " > 0 and "
- + _SYNC_VERSION + " IS NOT NULL))";
-
- public AbstractTableMerger(SQLiteDatabase database,
- String table, Uri tableURL, String deletedTable,
- Uri deletedTableURL)
- {
- mDb = database;
- mTable = table;
- mTableURL = tableURL;
- mDeletedTable = deletedTable;
- mDeletedTableURL = deletedTableURL;
- mValues = new ContentValues();
- }
-
- public abstract void insertRow(ContentProvider diffs,
- Cursor diffsCursor);
- public abstract void updateRow(long localPersonID,
- ContentProvider diffs, Cursor diffsCursor);
- public abstract void resolveRow(long localPersonID,
- String syncID, ContentProvider diffs, Cursor diffsCursor);
-
- /**
- * This is called when it is determined that a row should be deleted from the
- * ContentProvider. The localCursor is on a table from the local ContentProvider
- * and its current position is of the row that should be deleted. The localCursor
- * is only guaranteed to contain the BaseColumns.ID column so the implementation
- * of deleteRow() must query the database directly if other columns are needed.
- * <p>
- * It is the responsibility of the implementation of this method to ensure that the cursor
- * points to the next row when this method returns, either by calling Cursor.deleteRow() or
- * Cursor.next().
- *
- * @param localCursor The Cursor into the local table, which points to the row that
- * is to be deleted.
- */
- public void deleteRow(Cursor localCursor) {
- localCursor.deleteRow();
- }
-
- /**
- * After {@link #merge} has completed, this method is called to send
- * notifications to {@link android.database.ContentObserver}s of changes
- * to the containing {@link ContentProvider}. These notifications likely
- * do not want to request a sync back to the network.
- */
- protected abstract void notifyChanges();
-
- private static boolean findInCursor(Cursor cursor, int column, String id) {
- while (!cursor.isAfterLast() && !cursor.isNull(column)) {
- int comp = id.compareTo(cursor.getString(column));
- if (comp > 0) {
- cursor.moveToNext();
- continue;
- }
- return comp == 0;
- }
- return false;
- }
-
- public void onMergeCancelled() {
- mIsMergeCancelled = true;
- }
-
- /**
- * Carry out a merge of the given diffs, and add the results to
- * the given MergeResult. If we are the first merge to find
- * client-side diffs, we'll use the given ContentProvider to
- * construct a temporary instance to hold them.
- */
- public void merge(final SyncContext context,
- final Account account,
- final SyncableContentProvider serverDiffs,
- TempProviderSyncResult result,
- SyncResult syncResult, SyncableContentProvider temporaryInstanceFactory) {
- mIsMergeCancelled = false;
- if (serverDiffs != null) {
- if (!mDb.isDbLockedByCurrentThread()) {
- throw new IllegalStateException("this must be called from within a DB transaction");
- }
- mergeServerDiffs(context, account, serverDiffs, syncResult);
- notifyChanges();
- }
-
- if (result != null) {
- findLocalChanges(result, temporaryInstanceFactory, account, syncResult);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "merge complete");
- }
-
- /**
- * @hide this is public for testing purposes only
- */
- public void mergeServerDiffs(SyncContext context,
- Account account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
- boolean diffsArePartial = serverDiffs.getContainsDiffs();
- // mark the current rows so that we can distinguish these from new
- // inserts that occur during the merge
- mDb.update(mTable, mSyncMarkValues, null, null);
- if (mDeletedTable != null) {
- mDb.update(mDeletedTable, mSyncMarkValues, null, null);
- }
-
- Cursor localCursor = null;
- Cursor deletedCursor = null;
- Cursor diffsCursor = null;
- try {
- // load the local database entries, so we can merge them with the server
- final String[] accountSelectionArgs = new String[]{account.name, account.type};
- localCursor = mDb.query(mTable, syncDirtyProjection,
- SELECT_MARKED, accountSelectionArgs, null, null,
- mTable + "." + _SYNC_ID);
- if (mDeletedTable != null) {
- deletedCursor = mDb.query(mDeletedTable, syncIdAndVersionProjection,
- SELECT_MARKED, accountSelectionArgs, null, null,
- mDeletedTable + "." + _SYNC_ID);
- } else {
- deletedCursor =
- mDb.rawQuery("select 'a' as _sync_id, 'b' as _sync_version limit 0", null);
- }
-
- // Apply updates and insertions from the server
- diffsCursor = serverDiffs.query(mTableURL,
- null, null, null, mTable + "." + _SYNC_ID);
- int deletedSyncIDColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_ID);
- int deletedSyncVersionColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_VERSION);
- int serverSyncIDColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
- int serverSyncVersionColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_VERSION);
- int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
-
- String lastSyncId = null;
- int diffsCount = 0;
- int localCount = 0;
- localCursor.moveToFirst();
- deletedCursor.moveToFirst();
- while (diffsCursor.moveToNext()) {
- if (mIsMergeCancelled) {
- return;
- }
- mDb.yieldIfContended();
- String serverSyncId = diffsCursor.getString(serverSyncIDColumn);
- String serverSyncVersion = diffsCursor.getString(serverSyncVersionColumn);
- long localRowId = 0;
- String localSyncVersion = null;
-
- diffsCount++;
- context.setStatusText("Processing " + diffsCount + "/"
- + diffsCursor.getCount());
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "processing server entry " +
- diffsCount + ", " + serverSyncId);
-
- if (TRACE) {
- if (diffsCount == 10) {
- Debug.startMethodTracing("atmtrace");
- }
- if (diffsCount == 20) {
- Debug.stopMethodTracing();
- }
- }
-
- boolean conflict = false;
- boolean update = false;
- boolean insert = false;
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "found event with serverSyncID " + serverSyncId);
- }
- if (TextUtils.isEmpty(serverSyncId)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.e(TAG, "server entry doesn't have a serverSyncID");
- }
- continue;
- }
-
- // It is possible that the sync adapter wrote the same record multiple times,
- // e.g. if the same record came via multiple feeds. If this happens just ignore
- // the duplicate records.
- if (serverSyncId.equals(lastSyncId)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "skipping record with duplicate remote server id " + lastSyncId);
- }
- continue;
- }
- lastSyncId = serverSyncId;
-
- String localSyncID = null;
- boolean localSyncDirty = false;
-
- while (!localCursor.isAfterLast()) {
- if (mIsMergeCancelled) {
- return;
- }
- localCount++;
- localSyncID = localCursor.getString(2);
-
- // If the local record doesn't have a _sync_id then
- // it is new. Ignore it for now, we will send an insert
- // the the server later.
- if (TextUtils.isEmpty(localSyncID)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "local record " +
- localCursor.getLong(1) +
- " has no _sync_id, ignoring");
- }
- localCursor.moveToNext();
- localSyncID = null;
- continue;
- }
-
- int comp = serverSyncId.compareTo(localSyncID);
-
- // the local DB has a record that the server doesn't have
- if (comp > 0) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "local record " +
- localCursor.getLong(1) +
- " has _sync_id " + localSyncID +
- " that is < server _sync_id " + serverSyncId);
- }
- if (diffsArePartial) {
- localCursor.moveToNext();
- } else {
- deleteRow(localCursor);
- if (mDeletedTable != null) {
- mDb.delete(mDeletedTable, _SYNC_ID +"=?", new String[] {localSyncID});
- }
- syncResult.stats.numDeletes++;
- mDb.yieldIfContended();
- }
- localSyncID = null;
- continue;
- }
-
- // the server has a record that the local DB doesn't have
- if (comp < 0) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "local record " +
- localCursor.getLong(1) +
- " has _sync_id " + localSyncID +
- " that is > server _sync_id " + serverSyncId);
- }
- localSyncID = null;
- }
-
- // the server and the local DB both have this record
- if (comp == 0) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "local record " +
- localCursor.getLong(1) +
- " has _sync_id " + localSyncID +
- " that matches the server _sync_id");
- }
- localSyncDirty = localCursor.getInt(0) != 0;
- localRowId = localCursor.getLong(1);
- localSyncVersion = localCursor.getString(3);
- localCursor.moveToNext();
- }
-
- break;
- }
-
- // If this record is in the deleted table then update the server version
- // in the deleted table, if necessary, and then ignore it here.
- // We will send a deletion indication to the server down a
- // little further.
- if (findInCursor(deletedCursor, deletedSyncIDColumn, serverSyncId)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record " + serverSyncId + " is in the deleted table");
- }
- final String deletedSyncVersion = deletedCursor.getString(deletedSyncVersionColumn);
- if (!TextUtils.equals(deletedSyncVersion, serverSyncVersion)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setting version of deleted record " + serverSyncId + " to "
- + serverSyncVersion);
- }
- ContentValues values = new ContentValues();
- values.put(_SYNC_VERSION, serverSyncVersion);
- mDb.update(mDeletedTable, values, "_sync_id=?", new String[]{serverSyncId});
- }
- continue;
- }
-
- // If the _sync_local_id is present in the diffsCursor
- // then this record corresponds to a local record that was just
- // inserted into the server and the _sync_local_id is the row id
- // of the local record. Set these fields so that the next check
- // treats this record as an update, which will allow the
- // merger to update the record with the server's sync id
- if (!diffsCursor.isNull(serverSyncLocalIdColumn)) {
- localRowId = diffsCursor.getLong(serverSyncLocalIdColumn);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the remote record with sync id " + serverSyncId
- + " has a local sync id, " + localRowId);
- }
- localSyncID = serverSyncId;
- localSyncDirty = false;
- localSyncVersion = null;
- }
-
- if (!TextUtils.isEmpty(localSyncID)) {
- // An existing server item has changed
- // If serverSyncVersion is null, there is no edit URL;
- // server won't let this change be written.
- boolean recordChanged = (localSyncVersion == null) ||
- (serverSyncVersion == null) ||
- !serverSyncVersion.equals(localSyncVersion);
- if (recordChanged) {
- if (localSyncDirty) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record " + serverSyncId
- + " conflicts with local _sync_id " + localSyncID
- + ", local _id " + localRowId);
- }
- conflict = true;
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "remote record " +
- serverSyncId +
- " updates local _sync_id " +
- localSyncID + ", local _id " +
- localRowId);
- }
- update = true;
- }
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "Skipping update: localSyncVersion: " + localSyncVersion +
- ", serverSyncVersion: " + serverSyncVersion);
- }
- }
- } else {
- // the local db doesn't know about this record so add it
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record " + serverSyncId + " is new, inserting");
- }
- insert = true;
- }
-
- if (update) {
- updateRow(localRowId, serverDiffs, diffsCursor);
- syncResult.stats.numUpdates++;
- } else if (conflict) {
- resolveRow(localRowId, serverSyncId, serverDiffs, diffsCursor);
- syncResult.stats.numUpdates++;
- } else if (insert) {
- insertRow(serverDiffs, diffsCursor);
- syncResult.stats.numInserts++;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "processed " + diffsCount + " server entries");
- }
-
- // If tombstones aren't in use delete any remaining local rows that
- // don't have corresponding server rows. Keep the rows that don't
- // have a sync id since those were created locally and haven't been
- // synced to the server yet.
- if (!diffsArePartial) {
- while (!localCursor.isAfterLast() && !TextUtils.isEmpty(localCursor.getString(2))) {
- if (mIsMergeCancelled) {
- return;
- }
- localCount++;
- final String localSyncId = localCursor.getString(2);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "deleting local record " +
- localCursor.getLong(1) +
- " _sync_id " + localSyncId);
- }
- deleteRow(localCursor);
- if (mDeletedTable != null) {
- mDb.delete(mDeletedTable, _SYNC_ID + "=?", new String[] {localSyncId});
- }
- syncResult.stats.numDeletes++;
- mDb.yieldIfContended();
- }
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "checked " + localCount +
- " local entries");
- } finally {
- if (diffsCursor != null) diffsCursor.close();
- if (localCursor != null) localCursor.close();
- if (deletedCursor != null) deletedCursor.close();
- }
-
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "applying deletions from the server");
-
- // Apply deletions from the server
- if (mDeletedTableURL != null) {
- diffsCursor = serverDiffs.query(mDeletedTableURL, null, null, null, null);
- try {
- while (diffsCursor.moveToNext()) {
- if (mIsMergeCancelled) {
- return;
- }
- // delete all rows that match each element in the diffsCursor
- fullyDeleteMatchingRows(diffsCursor, account, syncResult);
- mDb.yieldIfContended();
- }
- } finally {
- diffsCursor.close();
- }
- }
- }
-
- private void fullyDeleteMatchingRows(Cursor diffsCursor, Account account,
- SyncResult syncResult) {
- int serverSyncIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
- final boolean deleteBySyncId = !diffsCursor.isNull(serverSyncIdColumn);
-
- // delete the rows explicitly so that the delete operation can be overridden
- final String[] selectionArgs;
- Cursor c = null;
- try {
- if (deleteBySyncId) {
- selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn),
- account.name, account.type};
- c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_SYNC_ID_AND_ACCOUNT,
- selectionArgs, null, null, null);
- } else {
- int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
- selectionArgs = new String[]{diffsCursor.getString(serverSyncLocalIdColumn)};
- c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_ID, selectionArgs,
- null, null, null);
- }
- c.moveToFirst();
- while (!c.isAfterLast()) {
- deleteRow(c); // advances the cursor
- syncResult.stats.numDeletes++;
- }
- } finally {
- if (c != null) c.close();
- }
- if (deleteBySyncId && mDeletedTable != null) {
- mDb.delete(mDeletedTable, SELECT_BY_SYNC_ID_AND_ACCOUNT, selectionArgs);
- }
- }
-
- /**
- * Converts cursor into a Map, using the correct types for the values.
- */
- protected void cursorRowToContentValues(Cursor cursor, ContentValues map) {
- DatabaseUtils.cursorRowToContentValues(cursor, map);
- }
-
- /**
- * Finds local changes, placing the results in the given result object.
- * @param temporaryInstanceFactory As an optimization for the case
- * where there are no client-side diffs, mergeResult may initially
- * have no {@link TempProviderSyncResult#tempContentProvider}. If this is
- * the first in the sequence of AbstractTableMergers to find
- * client-side diffs, it will use the given ContentProvider to
- * create a temporary instance and store its {@link
- * android.content.ContentProvider} in the mergeResult.
- * @param account
- * @param syncResult
- */
- private void findLocalChanges(TempProviderSyncResult mergeResult,
- SyncableContentProvider temporaryInstanceFactory, Account account,
- SyncResult syncResult) {
- SyncableContentProvider clientDiffs = mergeResult.tempContentProvider;
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client updates");
-
- final String[] accountSelectionArgs = new String[]{account.name, account.type};
-
- // Generate the client updates and insertions
- // Create a cursor for dirty records
- long numInsertsOrUpdates = 0;
- Cursor localChangesCursor = mDb.query(mTable, null, SELECT_UNSYNCED, accountSelectionArgs,
- null, null, null);
- try {
- numInsertsOrUpdates = localChangesCursor.getCount();
- while (localChangesCursor.moveToNext()) {
- if (mIsMergeCancelled) {
- return;
- }
- if (clientDiffs == null) {
- clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
- }
- mValues.clear();
- cursorRowToContentValues(localChangesCursor, mValues);
- mValues.remove("_id");
- DatabaseUtils.cursorLongToContentValues(localChangesCursor, "_id", mValues,
- _SYNC_LOCAL_ID);
- clientDiffs.insert(mTableURL, mValues);
- }
- } finally {
- localChangesCursor.close();
- }
-
- // Generate the client deletions
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client deletions");
- long numEntries = DatabaseUtils.queryNumEntries(mDb, mTable);
- long numDeletedEntries = 0;
- if (mDeletedTable != null) {
- Cursor deletedCursor = mDb.query(mDeletedTable,
- syncIdAndVersionProjection,
- _SYNC_ACCOUNT + "=? AND " + _SYNC_ACCOUNT_TYPE + "=? AND "
- + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
- null, null, mDeletedTable + "." + _SYNC_ID);
- try {
- numDeletedEntries = deletedCursor.getCount();
- while (deletedCursor.moveToNext()) {
- if (mIsMergeCancelled) {
- return;
- }
- if (clientDiffs == null) {
- clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
- }
- mValues.clear();
- DatabaseUtils.cursorRowToContentValues(deletedCursor, mValues);
- clientDiffs.insert(mDeletedTableURL, mValues);
- }
- } finally {
- deletedCursor.close();
- }
- }
-
- if (clientDiffs != null) {
- mergeResult.tempContentProvider = clientDiffs;
- }
- syncResult.stats.numDeletes += numDeletedEntries;
- syncResult.stats.numUpdates += numInsertsOrUpdates;
- syncResult.stats.numEntries += numEntries;
- }
-}
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index fb6091a..14bc5dd 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -17,10 +17,11 @@
package android.content;
import android.accounts.Account;
+import android.net.TrafficStats;
import android.os.Bundle;
-import android.os.Process;
-import android.os.NetStat;
import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
import android.util.EventLog;
import java.util.concurrent.atomic.AtomicInteger;
@@ -36,6 +37,12 @@ import java.util.concurrent.atomic.AtomicInteger;
* that the sync has been canceled.
*/
public abstract class AbstractThreadedSyncAdapter {
+ /**
+ * Kernel event log tag. Also listed in data/etc/event-log-tags.
+ * @Deprecated
+ */
+ public static final int LOG_SYNC_DETAILS = 2743;
+
private final Context mContext;
private final AtomicInteger mNumSyncStarts;
private final ISyncAdapterImpl mISyncAdapterImpl;
@@ -44,9 +51,6 @@ public abstract class AbstractThreadedSyncAdapter {
private SyncThread mSyncThread;
private final Object mSyncThreadLock = new Object();
- /** Kernel event log tag. Also listed in data/etc/event-log-tags. */
- public static final int LOG_SYNC_DETAILS = 2743;
- private static final String TAG = "Sync";
private final boolean mAutoInitialize;
/**
@@ -113,10 +117,16 @@ public abstract class AbstractThreadedSyncAdapter {
if (mSyncThread != null
&& mSyncThread.mSyncContext.getSyncContextBinder()
== syncContext.asBinder()) {
- mSyncThread.interrupt();
+ onSyncCanceled(mSyncThread);
}
}
}
+
+ public void initialize(Account account, String authority) throws RemoteException {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ startSync(null, authority, account, extras);
+ }
}
/**
@@ -129,8 +139,6 @@ public abstract class AbstractThreadedSyncAdapter {
private final String mAuthority;
private final Account mAccount;
private final Bundle mExtras;
- private long mInitialTxBytes;
- private long mInitialRxBytes;
private SyncThread(String name, SyncContext syncContext, String authority,
Account account, Bundle extras) {
@@ -149,9 +157,6 @@ public abstract class AbstractThreadedSyncAdapter {
}
SyncResult syncResult = new SyncResult();
- int uid = Process.myUid();
- mInitialTxBytes = NetStat.getUidTxBytes(uid);
- mInitialRxBytes = NetStat.getUidRxBytes(uid);
ContentProviderClient provider = null;
try {
provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
@@ -168,8 +173,6 @@ public abstract class AbstractThreadedSyncAdapter {
if (!isCanceled()) {
mSyncContext.onFinished(syncResult);
}
- onLogSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, syncResult);
// synchronize so that the assignment will be seen by other threads
// that also synchronize accesses to mSyncThread
synchronized (mSyncThreadLock) {
@@ -206,16 +209,13 @@ public abstract class AbstractThreadedSyncAdapter {
String authority, ContentProviderClient provider, SyncResult syncResult);
/**
- * Logs details on the sync.
- * Normally this will be overridden by a subclass that will provide
- * provider-specific details.
+ * Indicates that a sync operation has been canceled. This will be invoked on a separate
+ * thread than the sync thread and so you must consider the multi-threaded implications
+ * of the work that you do in this method.
*
- * @param bytesSent number of bytes the sync sent over the network
- * @param bytesReceived number of bytes the sync received over the network
- * @param result The SyncResult object holding info on the sync
- * @hide
+ * @param thread the thread that is running the sync operation to cancel
*/
- protected void onLogSyncDetails(long bytesSent, long bytesReceived, SyncResult result) {
- EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, "");
+ public void onSyncCanceled(Thread thread) {
+ thread.interrupt();
}
}
diff --git a/core/java/android/content/AsyncQueryHandler.java b/core/java/android/content/AsyncQueryHandler.java
index 0a4a804..882879b 100644
--- a/core/java/android/content/AsyncQueryHandler.java
+++ b/core/java/android/content/AsyncQueryHandler.java
@@ -38,7 +38,6 @@ public abstract class AsyncQueryHandler extends Handler {
private static final int EVENT_ARG_INSERT = 2;
private static final int EVENT_ARG_UPDATE = 3;
private static final int EVENT_ARG_DELETE = 4;
- private static final int EVENT_ARG_QUERY_ENTITIES = 5;
/* package */ final WeakReference<ContentResolver> mResolver;
@@ -93,18 +92,6 @@ public abstract class AsyncQueryHandler extends Handler {
args.result = cursor;
break;
- case EVENT_ARG_QUERY_ENTITIES:
- EntityIterator iterator = null;
- try {
- iterator = resolver.queryEntities(args.uri, args.selection,
- args.selectionArgs, args.orderBy);
- } catch (Exception e) {
- Log.w(TAG, e.toString());
- }
-
- args.result = iterator;
- break;
-
case EVENT_ARG_INSERT:
args.result = resolver.insert(args.uri, args.values);
break;
@@ -195,45 +182,6 @@ public abstract class AsyncQueryHandler extends Handler {
}
/**
- * This method begins an asynchronous query for an {@link EntityIterator}.
- * When the query is done {@link #onQueryEntitiesComplete} is called.
- *
- * @param token A token passed into {@link #onQueryComplete} to identify the
- * query.
- * @param cookie An object that gets passed into {@link #onQueryComplete}
- * @param uri The URI, using the content:// scheme, for the content to
- * retrieve.
- * @param selection A filter declaring which rows to return, formatted as an
- * SQL WHERE clause (excluding the WHERE itself). Passing null
- * will return all rows for the given URI.
- * @param selectionArgs You may include ?s in selection, which will be
- * replaced by the values from selectionArgs, in the order that
- * they appear in the selection. The values will be bound as
- * Strings.
- * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
- * (excluding the ORDER BY itself). Passing null will use the
- * default sort order, which may be unordered.
- * @hide
- */
- public void startQueryEntities(int token, Object cookie, Uri uri, String selection,
- String[] selectionArgs, String orderBy) {
- // Use the token as what so cancelOperations works properly
- Message msg = mWorkerThreadHandler.obtainMessage(token);
- msg.arg1 = EVENT_ARG_QUERY_ENTITIES;
-
- WorkerArgs args = new WorkerArgs();
- args.handler = this;
- args.uri = uri;
- args.selection = selection;
- args.selectionArgs = selectionArgs;
- args.orderBy = orderBy;
- args.cookie = cookie;
- msg.obj = args;
-
- mWorkerThreadHandler.sendMessage(msg);
- }
-
- /**
* Attempts to cancel operation that has not already started. Note that
* there is no guarantee that the operation will be canceled. They still may
* result in a call to on[Query/Insert/Update/Delete]Complete after this
@@ -340,18 +288,6 @@ public abstract class AsyncQueryHandler extends Handler {
}
/**
- * Called when an asynchronous query is completed.
- *
- * @param token The token to identify the query.
- * @param cookie The cookie object.
- * @param iterator The iterator holding the query results.
- * @hide
- */
- protected void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
- // Empty
- }
-
- /**
* Called when an asynchronous insert is completed.
*
* @param token the token to identify the query, passed in from
@@ -408,10 +344,6 @@ public abstract class AsyncQueryHandler extends Handler {
onQueryComplete(token, args.cookie, (Cursor) args.result);
break;
- case EVENT_ARG_QUERY_ENTITIES:
- onQueryEntitiesComplete(token, args.cookie, (EntityIterator)args.result);
- break;
-
case EVENT_ARG_INSERT:
onInsertComplete(token, args.cookie, (Uri) args.result);
break;
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index a341c9b..91b1c4e 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -67,6 +67,11 @@ import java.util.ArrayList;
* process a request is coming from.</p>
*/
public abstract class ContentProvider implements ComponentCallbacks {
+ /*
+ * Note: if you add methods to ContentProvider, you must add similar methods to
+ * MockContentProvider.
+ */
+
private Context mContext = null;
private int mMyUid;
private String mReadPermission;
@@ -75,6 +80,33 @@ public abstract class ContentProvider implements ComponentCallbacks {
private Transport mTransport = new Transport();
+ public ContentProvider() {
+ }
+
+ /**
+ * Constructor just for mocking.
+ *
+ * @param context A Context object which should be some mock instance (like the
+ * instance of {@link android.test.mock.MockContext}).
+ * @param readPermission The read permision you want this instance should have in the
+ * test, which is available via {@link #getReadPermission()}.
+ * @param writePermission The write permission you want this instance should have
+ * in the test, which is available via {@link #getWritePermission()}.
+ * @param pathPermissions The PathPermissions you want this instance should have
+ * in the test, which is available via {@link #getPathPermissions()}.
+ * @hide
+ */
+ public ContentProvider(
+ Context context,
+ String readPermission,
+ String writePermission,
+ PathPermission[] pathPermissions) {
+ mContext = context;
+ mReadPermission = readPermission;
+ mWritePermission = writePermission;
+ mPathPermissions = pathPermissions;
+ }
+
/**
* Given an IContentProvider, try to coerce it back to the real
* ContentProvider object if it is running in the local process. This can
@@ -131,15 +163,6 @@ public abstract class ContentProvider implements ComponentCallbacks {
selectionArgs, sortOrder);
}
- /**
- * @hide
- */
- public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
- String sortOrder) {
- enforceReadPermission(uri);
- return ContentProvider.this.queryEntities(uri, selection, selectionArgs, sortOrder);
- }
-
public String getType(Uri uri) {
return ContentProvider.this.getType(uri);
}
@@ -445,14 +468,6 @@ public abstract class ContentProvider implements ComponentCallbacks {
String selection, String[] selectionArgs, String sortOrder);
/**
- * @hide
- */
- public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException();
- }
-
- /**
* Return the MIME type of the data at the given URI. This should start with
* <code>vnd.android.cursor.item</code> for a single record,
* or <code>vnd.android.cursor.dir/</code> for multiple items.
@@ -549,7 +564,7 @@ public abstract class ContentProvider implements ComponentCallbacks {
/**
* Open a file blob associated with a content URI.
* This method can be called from multiple
- * threads, as described inentity
+ * threads, as described in
* <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
* Processes and Threads</a>.
*
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 403c4d8..0858ea5 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -89,16 +89,7 @@ public class ContentProviderClient {
return mContentProvider.openAssetFile(url, mode);
}
- /**
- * see {@link ContentProvider#queryEntities}
- * @hide
- */
- public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
- String sortOrder) throws RemoteException {
- return mContentProvider.queryEntities(uri, selection, selectionArgs, sortOrder);
- }
-
- /** see {@link ContentProvider#applyBatch} */
+ /** see {@link ContentProvider#applyBatch} */
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
return mContentProvider.applyBatch(operations);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index adc3f60..bacb684 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -106,20 +106,6 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
return true;
}
- case QUERY_ENTITIES_TRANSACTION:
- {
- data.enforceInterface(IContentProvider.descriptor);
- Uri url = Uri.CREATOR.createFromParcel(data);
- String selection = data.readString();
- String[] selectionArgs = data.readStringArray();
- String sortOrder = data.readString();
- EntityIterator entityIterator = queryEntities(url, selection, selectionArgs,
- sortOrder);
- reply.writeNoException();
- reply.writeStrongBinder(new IEntityIteratorImpl(entityIterator).asBinder());
- return true;
- }
-
case GET_TYPE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
@@ -245,32 +231,6 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
return super.onTransact(code, data, reply, flags);
}
- /**
- * @hide
- */
- private class IEntityIteratorImpl extends IEntityIterator.Stub {
- private final EntityIterator mEntityIterator;
-
- IEntityIteratorImpl(EntityIterator iterator) {
- mEntityIterator = iterator;
- }
- public boolean hasNext() throws RemoteException {
- return mEntityIterator.hasNext();
- }
-
- public Entity next() throws RemoteException {
- return mEntityIterator.next();
- }
-
- public void reset() throws RemoteException {
- mEntityIterator.reset();
- }
-
- public void close() throws RemoteException {
- mEntityIterator.close();
- }
- }
-
public IBinder asBinder()
{
return this;
@@ -352,64 +312,6 @@ final class ContentProviderProxy implements IContentProvider
return adaptor;
}
- /**
- * @hide
- */
- public EntityIterator queryEntities(Uri url, String selection, String[] selectionArgs,
- String sortOrder)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
-
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- data.writeString(selection);
- data.writeStringArray(selectionArgs);
- data.writeString(sortOrder);
-
- mRemote.transact(IContentProvider.QUERY_ENTITIES_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
-
- IBinder entityIteratorBinder = reply.readStrongBinder();
-
- data.recycle();
- reply.recycle();
-
- return new RemoteEntityIterator(IEntityIterator.Stub.asInterface(entityIteratorBinder));
- }
-
- /**
- * @hide
- */
- static class RemoteEntityIterator implements EntityIterator {
- private final IEntityIterator mEntityIterator;
- RemoteEntityIterator(IEntityIterator entityIterator) {
- mEntityIterator = entityIterator;
- }
-
- public boolean hasNext() throws RemoteException {
- return mEntityIterator.hasNext();
- }
-
- public Entity next() throws RemoteException {
- return mEntityIterator.next();
- }
-
- public void reset() throws RemoteException {
- mEntityIterator.reset();
- }
-
- public void close() {
- try {
- mEntityIterator.close();
- } catch (RemoteException e) {
- // doesn't matter
- }
- }
- }
-
public String getType(Uri url) throws RemoteException
{
Parcel data = Parcel.obtain();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index c4b0807..eb2d7b1 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -88,11 +88,11 @@ public abstract class ContentResolver {
* <code>content://com.company.provider.imap/inbox/1</code> for a particular
* message in the inbox, whose MIME type would be reported as
* <code>CURSOR_ITEM_BASE_TYPE + "/vnd.company.imap-msg"</code>
- *
+ *
* <p>Compare with {@link #CURSOR_DIR_BASE_TYPE}.
*/
public static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
-
+
/**
* This is the Android platform's base MIME type for a content: URI
* containing a Cursor of zero or more items. Applications should use this
@@ -102,7 +102,7 @@ public abstract class ContentResolver {
* <code>content://com.company.provider.imap/inbox</code> for all of the
* messages in its inbox, whose MIME type would be reported as
* <code>CURSOR_DIR_BASE_TYPE + "/vnd.company.imap-msg"</code>
- *
+ *
* <p>Note how the base MIME type varies between this and
* {@link #CURSOR_ITEM_BASE_TYPE} depending on whether there is
* one single item or multiple items in the data set, while the sub-type
@@ -173,13 +173,25 @@ public abstract class ContentResolver {
}
/**
+ * <p>
* Query the given URI, returning a {@link Cursor} over the result set.
+ * </p>
+ * <p>
+ * For best performance, the caller should follow these guidelines:
+ * <ul>
+ * <li>Provide an explicit projection, to prevent
+ * reading data from storage that aren't going to be used.</li>
+ * <li>Use question mark parameter markers such as 'phone=?' instead of
+ * explicit values in the {@code selection} parameter, so that queries
+ * that differ only by those values will be recognized as the same
+ * for caching purposes.</li>
+ * </ul>
+ * </p>
*
* @param uri The URI, using the content:// scheme, for the content to
* retrieve.
* @param projection A list of which columns to return. Passing null will
- * return all columns, which is discouraged to prevent reading data
- * from storage that isn't going to be used.
+ * return all columns, which is inefficient.
* @param selection A filter declaring which rows to return, formatted as an
* SQL WHERE clause (excluding the WHERE itself). Passing null will
* return all rows for the given URI.
@@ -216,96 +228,6 @@ public abstract class ContentResolver {
}
/**
- * EntityIterator wrapper that releases the associated ContentProviderClient when the
- * iterator is closed.
- * @hide
- */
- private class EntityIteratorWrapper implements EntityIterator {
- private final EntityIterator mInner;
- private final ContentProviderClient mClient;
- private volatile boolean mClientReleased;
-
- EntityIteratorWrapper(EntityIterator inner, ContentProviderClient client) {
- mInner = inner;
- mClient = client;
- mClientReleased = false;
- }
-
- public boolean hasNext() throws RemoteException {
- if (mClientReleased) {
- throw new IllegalStateException("this iterator is already closed");
- }
- return mInner.hasNext();
- }
-
- public Entity next() throws RemoteException {
- if (mClientReleased) {
- throw new IllegalStateException("this iterator is already closed");
- }
- return mInner.next();
- }
-
- public void reset() throws RemoteException {
- if (mClientReleased) {
- throw new IllegalStateException("this iterator is already closed");
- }
- mInner.reset();
- }
-
- public void close() {
- mClient.release();
- mInner.close();
- mClientReleased = true;
- }
-
- protected void finalize() throws Throwable {
- if (!mClientReleased) {
- mClient.release();
- }
- super.finalize();
- }
- }
-
- /**
- * Query the given URI, returning an {@link EntityIterator} over the result set.
- *
- * @param uri The URI, using the content:// scheme, for the content to
- * retrieve.
- * @param selection A filter declaring which rows to return, formatted as an
- * SQL WHERE clause (excluding the WHERE itself). Passing null will
- * return all rows for the given URI.
- * @param selectionArgs You may include ?s in selection, which will be
- * replaced by the values from selectionArgs, in the order that they
- * appear in the selection. The values will be bound as Strings.
- * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
- * clause (excluding the ORDER BY itself). Passing null will use the
- * default sort order, which may be unordered.
- * @return An EntityIterator object
- * @throws RemoteException thrown if a RemoteException is encountered while attempting
- * to communicate with a remote provider.
- * @throws IllegalArgumentException thrown if there is no provider that matches the uri
- * @hide
- */
- public final EntityIterator queryEntities(Uri uri,
- String selection, String[] selectionArgs, String sortOrder) throws RemoteException {
- ContentProviderClient provider = acquireContentProviderClient(uri);
- if (provider == null) {
- throw new IllegalArgumentException("Unknown URL " + uri);
- }
- try {
- EntityIterator entityIterator =
- provider.queryEntities(uri, selection, selectionArgs, sortOrder);
- return new EntityIteratorWrapper(entityIterator, provider);
- } catch(RuntimeException e) {
- provider.release();
- throw e;
- } catch(RemoteException e) {
- provider.release();
- throw e;
- }
- }
-
- /**
* Open a stream on to the content associated with a content URI. If there
* is no data associated with the URI, FileNotFoundException is thrown.
*
@@ -315,10 +237,10 @@ public abstract class ContentResolver {
* <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
* <li>file ({@link #SCHEME_FILE})</li>
* </ul>
- *
+ *
* <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
* on these schemes.
- *
+ *
* @param uri The desired URI.
* @return InputStream
* @throws FileNotFoundException if the provided URI could not be opened.
@@ -373,7 +295,7 @@ public abstract class ContentResolver {
*
* <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
* on these schemes.
- *
+ *
* @param uri The desired URI.
* @param mode May be "w", "wa", "rw", or "rwt".
* @return OutputStream
@@ -408,7 +330,7 @@ public abstract class ContentResolver {
*
* <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
* on these schemes.
- *
+ *
* @param uri The desired URI to open.
* @param mode The file mode to use, as per {@link ContentProvider#openFile
* ContentProvider.openFile}.
@@ -424,19 +346,19 @@ public abstract class ContentResolver {
if (afd == null) {
return null;
}
-
+
if (afd.getDeclaredLength() < 0) {
// This is a full file!
return afd.getParcelFileDescriptor();
}
-
+
// Client can't handle a sub-section of a file, so close what
// we got and bail with an exception.
try {
afd.close();
} catch (IOException e) {
}
-
+
throw new FileNotFoundException("Not a whole file");
}
@@ -581,7 +503,7 @@ public abstract class ContentResolver {
res.id = id;
return res;
}
-
+
/** @hide */
static public int modeToMode(Uri uri, String mode) throws FileNotFoundException {
int modeBits;
@@ -608,7 +530,7 @@ public abstract class ContentResolver {
}
return modeBits;
}
-
+
/**
* Inserts a row into a table at the given URL.
*
@@ -1236,7 +1158,7 @@ public abstract class ContentResolver {
/** @hide */
public static final String CONTENT_SERVICE_NAME = "content";
-
+
/** @hide */
public static IContentService getContentService() {
if (sContentService != null) {
@@ -1248,7 +1170,7 @@ public abstract class ContentResolver {
if (Config.LOGV) Log.v("ContentService", "default service = " + sContentService);
return sContentService;
}
-
+
private static IContentService sContentService;
private final Context mContext;
private static final String TAG = "ContentResolver";
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 799bc22..0fafe5d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -104,6 +104,18 @@ public abstract class Context {
*/
public static final int BIND_DEBUG_UNBIND = 0x0002;
+ /**
+ * Flag for {@link #bindService}: don't allow this binding to raise
+ * the target service's process to the foreground scheduling priority.
+ * It will still be raised to the at least the same memory priority
+ * as the client (so that its process will not be killable in any
+ * situation where the client is not killable), but for CPU scheduling
+ * purposes it may be left in the background. This only has an impact
+ * in the situation where the binding client is a foreground process
+ * and the target service is in a background process.
+ */
+ public static final int BIND_NOT_FOREGROUND = 0x0004;
+
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
@@ -1302,23 +1314,31 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@blink android.appwidget.AppWidgetManager} for accessing AppWidgets.
+ * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets.
*
* @hide
* @see #getSystemService
*/
public static final String APPWIDGET_SERVICE = "appwidget";
-
+
/**
* Use with {@link #getSystemService} to retrieve an
- * {@blink android.backup.IBackupManager IBackupManager} for communicating
+ * {@link android.backup.IBackupManager IBackupManager} for communicating
* with the backup mechanism.
* @hide
*
* @see #getSystemService
*/
public static final String BACKUP_SERVICE = "backup";
-
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.os.DropBoxManager} instance for recording
+ * diagnostic logs.
+ * @see #getSystemService
+ */
+ public static final String DROPBOX_SERVICE = "dropbox";
+
/**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
diff --git a/core/java/android/content/CursorEntityIterator.java b/core/java/android/content/CursorEntityIterator.java
new file mode 100644
index 0000000..0c66646
--- /dev/null
+++ b/core/java/android/content/CursorEntityIterator.java
@@ -0,0 +1,88 @@
+package android.content;
+
+import android.database.Cursor;
+import android.os.RemoteException;
+
+/**
+ * Abstract implementation of EntityIterator that makes it easy to wrap a cursor
+ * that can contain several consecutive rows for an entity.
+ * @hide
+ */
+public abstract class CursorEntityIterator implements EntityIterator {
+ private final Cursor mCursor;
+ private boolean mIsClosed;
+
+ /**
+ * Constructor that makes initializes the cursor such that the iterator points to the
+ * first Entity, if there are any.
+ * @param cursor the cursor that contains the rows that make up the entities
+ */
+ public CursorEntityIterator(Cursor cursor) {
+ mIsClosed = false;
+ mCursor = cursor;
+ mCursor.moveToFirst();
+ }
+
+ /**
+ * Returns the entity that the cursor is currently pointing to. This must take care to advance
+ * the cursor past this entity. This will never be called if the cursor is at the end.
+ * @param cursor the cursor that contains the entity rows
+ * @return the entity that the cursor is currently pointing to
+ * @throws RemoteException if a RemoteException is caught while attempting to build the Entity
+ */
+ public abstract Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException;
+
+ /**
+ * Returns whether there are more elements to iterate, i.e. whether the
+ * iterator is positioned in front of an element.
+ *
+ * @return {@code true} if there are more elements, {@code false} otherwise.
+ * @see #next
+ */
+ public final boolean hasNext() throws RemoteException {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling hasNext() when the iterator is closed");
+ }
+
+ return !mCursor.isAfterLast();
+ }
+
+ /**
+ * Returns the next object in the iteration, i.e. returns the element in
+ * front of the iterator and advances the iterator by one position.
+ *
+ * @return the next object.
+ * @throws java.util.NoSuchElementException
+ * if there are no more elements.
+ * @see #hasNext
+ */
+ public Entity next() throws RemoteException {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling next() when the iterator is closed");
+ }
+ if (!hasNext()) {
+ throw new IllegalStateException("you may only call next() if hasNext() is true");
+ }
+
+ return getEntityAndIncrementCursor(mCursor);
+ }
+
+ public final void reset() throws RemoteException {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling reset() when the iterator is closed");
+ }
+ mCursor.moveToFirst();
+ }
+
+ /**
+ * Indicates that this iterator is no longer needed and that any associated resources
+ * may be released (such as a SQLite cursor).
+ */
+ public final void close() {
+ if (mIsClosed) {
+ throw new IllegalStateException("closing when already closed");
+ }
+ mIsClosed = true;
+ mCursor.close();
+ }
+}
diff --git a/core/java/android/content/Entity.aidl b/core/java/android/content/Entity.aidl
deleted file mode 100644
index fb201f3..0000000
--- a/core/java/android/content/Entity.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/* //device/java/android/android/content/Entity.aidl
-**
-** Copyright 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.content;
-
-parcelable Entity;
diff --git a/core/java/android/content/Entity.java b/core/java/android/content/Entity.java
index ee8112e..7842de0 100644
--- a/core/java/android/content/Entity.java
+++ b/core/java/android/content/Entity.java
@@ -24,11 +24,13 @@ import android.util.Log;
import java.util.ArrayList;
/**
- * Objects that pass through the ContentProvider and ContentResolver's methods that deal with
- * Entities must implement this abstract base class and thus themselves be Parcelable.
- * @hide
+ * A representation of a item using ContentValues. It contains one top level ContentValue
+ * plus a collection of Uri, ContentValues tuples as subvalues. One example of its use
+ * is in Contacts, where the top level ContentValue contains the columns from the RawContacts
+ * table and the subvalues contain a ContentValues object for each row from the Data table that
+ * corresponds to that RawContact. The uri refers to the Data table uri for each row.
*/
-public final class Entity implements Parcelable {
+public final class Entity {
final private ContentValues mValues;
final private ArrayList<NamedContentValues> mSubValues;
@@ -49,40 +51,6 @@ public final class Entity implements Parcelable {
mSubValues.add(new Entity.NamedContentValues(uri, values));
}
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- mValues.writeToParcel(dest, 0);
- dest.writeInt(mSubValues.size());
- for (NamedContentValues value : mSubValues) {
- value.uri.writeToParcel(dest, 0);
- value.values.writeToParcel(dest, 0);
- }
- }
-
- private Entity(Parcel source) {
- mValues = ContentValues.CREATOR.createFromParcel(source);
- final int numValues = source.readInt();
- mSubValues = new ArrayList<NamedContentValues>(numValues);
- for (int i = 0; i < numValues; i++) {
- final Uri uri = Uri.CREATOR.createFromParcel(source);
- final ContentValues values = ContentValues.CREATOR.createFromParcel(source);
- mSubValues.add(new NamedContentValues(uri, values));
- }
- }
-
- public static final Creator<Entity> CREATOR = new Creator<Entity>() {
- public Entity createFromParcel(Parcel source) {
- return new Entity(source);
- }
-
- public Entity[] newArray(int size) {
- return new Entity[size];
- }
- };
-
public static class NamedContentValues {
public final Uri uri;
public final ContentValues values;
diff --git a/core/java/android/content/EntityIterator.java b/core/java/android/content/EntityIterator.java
index 1b73439..3cc1040 100644
--- a/core/java/android/content/EntityIterator.java
+++ b/core/java/android/content/EntityIterator.java
@@ -18,9 +18,6 @@ package android.content;
import android.os.RemoteException;
-/**
- * @hide
- */
public interface EntityIterator {
/**
* Returns whether there are more elements to iterate, i.e. whether the
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 0798adf..1b0ca58 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -44,12 +44,6 @@ public interface IContentProvider extends IInterface {
CursorWindow window) throws RemoteException;
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException;
- /**
- * @hide
- */
- public EntityIterator queryEntities(Uri url, String selection,
- String[] selectionArgs, String sortOrder)
- throws RemoteException;
public String getType(Uri url) throws RemoteException;
public Uri insert(Uri url, ContentValues initialValues)
throws RemoteException;
@@ -76,9 +70,5 @@ public interface IContentProvider extends IInterface {
static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12;
static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13;
static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
- /**
- * @hide
- */
- static final int QUERY_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 18;
static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19;
}
diff --git a/core/java/android/content/IEntityIterator.java b/core/java/android/content/IEntityIterator.java
deleted file mode 100644
index 068581e..0000000
--- a/core/java/android/content/IEntityIterator.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * 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 android.content;
-
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.Parcelable;
-import android.util.Log;
-
-/**
- * ICPC interface methods for an iterator over Entity objects.
- * @hide
- */
-public interface IEntityIterator extends IInterface {
- /** Local-side IPC implementation stub class. */
- public static abstract class Stub extends Binder implements IEntityIterator {
- private static final String TAG = "IEntityIterator";
- private static final java.lang.String DESCRIPTOR = "android.content.IEntityIterator";
-
- /** Construct the stub at attach it to the interface. */
- public Stub() {
- this.attachInterface(this, DESCRIPTOR);
- }
- /**
- * Cast an IBinder object into an IEntityIterator interface,
- * generating a proxy if needed.
- */
- public static IEntityIterator asInterface(IBinder obj) {
- if ((obj==null)) {
- return null;
- }
- IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin!=null)&&(iin instanceof IEntityIterator))) {
- return ((IEntityIterator)iin);
- }
- return new IEntityIterator.Stub.Proxy(obj);
- }
-
- public IBinder asBinder() {
- return this;
- }
-
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- switch (code) {
- case INTERFACE_TRANSACTION:
- {
- reply.writeString(DESCRIPTOR);
- return true;
- }
-
- case TRANSACTION_hasNext:
- {
- data.enforceInterface(DESCRIPTOR);
- boolean _result;
- try {
- _result = this.hasNext();
- } catch (Exception e) {
- Log.e(TAG, "caught exception in hasNext()", e);
- reply.writeException(e);
- return true;
- }
- reply.writeNoException();
- reply.writeInt(((_result)?(1):(0)));
- return true;
- }
-
- case TRANSACTION_next:
- {
- data.enforceInterface(DESCRIPTOR);
- Entity entity;
- try {
- entity = this.next();
- } catch (RemoteException e) {
- Log.e(TAG, "caught exception in next()", e);
- reply.writeException(e);
- return true;
- }
- reply.writeNoException();
- entity.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- return true;
- }
-
- case TRANSACTION_reset:
- {
- data.enforceInterface(DESCRIPTOR);
- try {
- this.reset();
- } catch (RemoteException e) {
- Log.e(TAG, "caught exception in next()", e);
- reply.writeException(e);
- return true;
- }
- reply.writeNoException();
- return true;
- }
-
- case TRANSACTION_close:
- {
- data.enforceInterface(DESCRIPTOR);
- try {
- this.close();
- } catch (RemoteException e) {
- Log.e(TAG, "caught exception in close()", e);
- reply.writeException(e);
- return true;
- }
- reply.writeNoException();
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
-
- private static class Proxy implements IEntityIterator {
- private IBinder mRemote;
- Proxy(IBinder remote) {
- mRemote = remote;
- }
- public IBinder asBinder() {
- return mRemote;
- }
- public java.lang.String getInterfaceDescriptor() {
- return DESCRIPTOR;
- }
- public boolean hasNext() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- boolean _result;
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_hasNext, _data, _reply, 0);
- _reply.readException();
- _result = (0!=_reply.readInt());
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- return _result;
- }
-
- public Entity next() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_next, _data, _reply, 0);
- _reply.readException();
- return Entity.CREATOR.createFromParcel(_reply);
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }
-
- public void reset() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_reset, _data, _reply, 0);
- _reply.readException();
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }
-
- public void close() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_close, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- }
- }
- static final int TRANSACTION_hasNext = (IBinder.FIRST_CALL_TRANSACTION + 0);
- static final int TRANSACTION_next = (IBinder.FIRST_CALL_TRANSACTION + 1);
- static final int TRANSACTION_close = (IBinder.FIRST_CALL_TRANSACTION + 2);
- static final int TRANSACTION_reset = (IBinder.FIRST_CALL_TRANSACTION + 3);
- }
- public boolean hasNext() throws RemoteException;
- public Entity next() throws RemoteException;
- public void reset() throws RemoteException;
- public void close() throws RemoteException;
-}
diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl
index 4660527..dd9d14e 100644
--- a/core/java/android/content/ISyncAdapter.aidl
+++ b/core/java/android/content/ISyncAdapter.aidl
@@ -44,4 +44,12 @@ oneway interface ISyncAdapter {
* @param syncContext the ISyncContext that was passed to {@link #startSync}
*/
void cancelSync(ISyncContext syncContext);
+
+ /**
+ * Initialize the SyncAdapter for this account and authority.
+ *
+ * @param account the account that should be synced
+ * @param authority the authority that should be synced
+ */
+ void initialize(in Account account, String authority);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a96e896..bf37b62 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -34,7 +34,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import java.io.IOException;
import java.io.Serializable;
@@ -575,7 +575,7 @@ import java.util.Set;
* {@link #setFlags} and {@link #addFlags}. See {@link #setFlags} for a list
* of all possible flags.
*/
-public class Intent implements Parcelable {
+public class Intent implements Parcelable, Cloneable {
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent activity actions (see action variable).
@@ -1102,6 +1102,16 @@ public class Intent implements Parcelable {
public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS";
/**
+ * Activity Action: Start the global search activity.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ *
+ * @hide Pending API council approval
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_GLOBAL_SEARCH = "android.intent.action.GLOBAL_SEARCH";
+
+ /**
* Activity Action: The user pressed the "Report" button in the crash/ANR dialog.
* This intent is delivered to the package which installed the application, usually
* the Market.
@@ -1120,7 +1130,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
-
+
/**
* Activity Action: Setup wizard to launch after a platform update. This
* activity should have a string meta-data field associated with it,
@@ -1134,7 +1144,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
-
+
/**
* A string associated with a {@link #ACTION_UPGRADE_SETUP} activity
* describing the last run version of the platform that was setup.
@@ -1148,7 +1158,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: Sent after the screen turns off.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1156,7 +1166,7 @@ public class Intent implements Parcelable {
public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
/**
* Broadcast Action: Sent after the screen turns on.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1166,12 +1176,12 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: Sent when the user is present after device wakes up (e.g when the
* keyguard is gone).
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_USER_PRESENT= "android.intent.action.USER_PRESENT";
+ public static final String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
/**
* Broadcast Action: The current time has changed. Sent every
@@ -1179,7 +1189,7 @@ public class Intent implements Parcelable {
* in manifests, only by exlicitly registering for it with
* {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
* Context.registerReceiver()}.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1200,7 +1210,7 @@ public class Intent implements Parcelable {
* <ul>
* <li><em>time-zone</em> - The java.util.TimeZone.getID() value identifying the new time zone.</li>
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1228,7 +1238,7 @@ public class Intent implements Parcelable {
* such as installing alarms. You must hold the
* {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission
* in order to receive this broadcast.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1244,7 +1254,7 @@ public class Intent implements Parcelable {
* Broadcast Action: Trigger the download and eventual installation
* of a package.
* <p>Input: {@link #getData} is the URI of the package file to download.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1260,7 +1270,7 @@ public class Intent implements Parcelable {
* <li> {@link #EXTRA_REPLACING} is set to true if this is following
* an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1274,7 +1284,7 @@ public class Intent implements Parcelable {
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1292,7 +1302,7 @@ public class Intent implements Parcelable {
* <li> {@link #EXTRA_REPLACING} is set to true if this will be followed
* by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1309,7 +1319,7 @@ public class Intent implements Parcelable {
* <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the
* default action of restarting the application.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1325,7 +1335,7 @@ public class Intent implements Parcelable {
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1340,7 +1350,7 @@ public class Intent implements Parcelable {
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1349,7 +1359,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: A user ID has been removed from the system. The user
* ID number is stored in the extra data under {@link #EXTRA_UID}.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1370,13 +1380,13 @@ public class Intent implements Parcelable {
* application to make sure it sees the new changes. Some system code that
* can not be restarted will need to watch for this action and handle it
* appropriately.
- *
+ *
* <p class="note">
* You can <em>not</em> receive this through components declared
* in manifests, only by explicitly registering for it with
* {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
* Context.registerReceiver()}.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
@@ -1386,7 +1396,7 @@ public class Intent implements Parcelable {
public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
/**
* Broadcast Action: The current device's locale has changed.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1407,7 +1417,7 @@ public class Intent implements Parcelable {
* and {@link #ACTION_POWER_DISCONNECTED} for distinct battery-related
* broadcasts that are sent and can be received through manifest
* receivers.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1416,7 +1426,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: Indicates low battery condition on the device.
* This broadcast corresponds to the "Low battery warning" system dialog.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1426,7 +1436,7 @@ public class Intent implements Parcelable {
* Broadcast Action: Indicates the battery is now okay after being low.
* This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has
* gone back up to an okay state.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1438,7 +1448,7 @@ public class Intent implements Parcelable {
* Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
* stay active to receive this notification. This action can be used to implement actions
* that wait until power is available to trigger.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1450,7 +1460,7 @@ public class Intent implements Parcelable {
* Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
* stay active to receive this notification. This action can be used to implement actions
* that wait until power is available to trigger.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1463,7 +1473,7 @@ public class Intent implements Parcelable {
* off, not sleeping). Once the broadcast is complete, the final shutdown
* will proceed and all unsaved data lost. Apps will not normally need
* to handle this, since the foreground activity will be paused as well.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1483,7 +1493,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: A sticky broadcast that indicates low memory
* condition on the device
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1491,7 +1501,7 @@ public class Intent implements Parcelable {
public static final String ACTION_DEVICE_STORAGE_LOW = "android.intent.action.DEVICE_STORAGE_LOW";
/**
* Broadcast Action: Indicates low memory condition on the device no longer exists
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1658,7 +1668,7 @@ public class Intent implements Parcelable {
* then cell radio and possibly other radios such as bluetooth or WiFi may have also been
* turned off</li>
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1740,7 +1750,7 @@ public class Intent implements Parcelable {
* <p>You must hold the
* {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
* permission to receive this Intent.</p>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1751,7 +1761,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: Have the device reboot. This is only for use by
* system code.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -2102,7 +2112,7 @@ public class Intent implements Parcelable {
* indicate that the dock should take over the home key when it is active.
*/
public static final String METADATA_DOCK_HOME = "android.dock_home";
-
+
/**
* Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing
* the bug report.
@@ -2389,6 +2399,20 @@ public class Intent implements Parcelable {
*/
public static final int FLAG_RECEIVER_REGISTERED_ONLY = 0x40000000;
/**
+ * If set, when sending a broadcast the new broadcast will replace
+ * any existing pending broadcast that matches it. Matching is defined
+ * by {@link Intent#filterEquals(Intent) Intent.filterEquals} returning
+ * true for the intents of the two broadcasts. When a match is found,
+ * the new broadcast (and receivers associated with it) will replace the
+ * existing one in the pending broadcast list, remaining at the same
+ * position in the list.
+ *
+ * <p>This flag is most typically used with sticky broadcasts, which
+ * only care about delivering the most recent values of the broadcast
+ * to their receivers.
+ */
+ public static final int FLAG_RECEIVER_REPLACE_PENDING = 0x20000000;
+ /**
* If set, when sending a broadcast <i>before boot has completed</i> only
* registered receivers will be called -- no BroadcastReceiver components
* will be launched. Sticky intent state will be recorded properly even
@@ -2401,14 +2425,14 @@ public class Intent implements Parcelable {
*
* @hide
*/
- public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000;
+ public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x10000000;
/**
* Set when this broadcast is for a boot upgrade, a special mode that
* allows the broadcast to be sent before the system is ready and launches
* the app process with no providers running in it.
* @hide
*/
- public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x10000000;
+ public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x08000000;
/**
* @hide Flags that can't be changed with PendingIntent.
@@ -2416,7 +2440,7 @@ public class Intent implements Parcelable {
public static final int IMMUTABLE_FLAGS =
FLAG_GRANT_READ_URI_PERMISSION
| FLAG_GRANT_WRITE_URI_PERMISSION;
-
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// toUri() and parseUri() options.
@@ -2430,7 +2454,7 @@ public class Intent implements Parcelable {
* VIEW action for that raw URI.
*/
public static final int URI_INTENT_SCHEME = 1<<0;
-
+
// ---------------------------------------------------------------------
private String mAction;
@@ -2589,7 +2613,7 @@ public class Intent implements Parcelable {
public static Intent getIntent(String uri) throws URISyntaxException {
return parseUri(uri, 0);
}
-
+
/**
* Create an intent from a URI. This URI may encode the action,
* category, and other intent fields, if it was returned by
@@ -2608,7 +2632,7 @@ public class Intent implements Parcelable {
* @throws URISyntaxException Throws URISyntaxError if the basic URI syntax
* it bad (as parsed by the Uri class) or the Intent data within the
* URI is invalid.
- *
+ *
* @see #toUri
*/
public static Intent parseUri(String uri, int flags) throws URISyntaxException {
@@ -2626,7 +2650,7 @@ public class Intent implements Parcelable {
return intent;
}
}
-
+
// simple case
i = uri.lastIndexOf("#");
if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
@@ -2718,7 +2742,7 @@ public class Intent implements Parcelable {
data = scheme + ':' + data;
}
}
-
+
if (data.length() > 0) {
try {
intent.mData = Uri.parse(data);
@@ -2727,7 +2751,7 @@ public class Intent implements Parcelable {
}
}
}
-
+
return intent;
} catch (IndexOutOfBoundsException e) {
@@ -2878,7 +2902,7 @@ public class Intent implements Parcelable {
} else {
intent.mData = Uri.parse(uri);
}
-
+
if (intent.mAction == null) {
// By default, if no action is specified, then use VIEW.
intent.mAction = ACTION_VIEW;
@@ -5103,13 +5127,13 @@ public class Intent implements Parcelable {
* used with {@link Uri#parse Uri.parse(String)}. The URI contains the
* Intent's data as the base URI, with an additional fragment describing
* the action, categories, type, flags, package, component, and extras.
- *
+ *
* <p>You can convert the returned string back to an Intent with
* {@link #getIntent}.
- *
+ *
* @param flags Additional operating flags. Either 0 or
* {@link #URI_INTENT_SCHEME}.
- *
+ *
* @return Returns a URI encoding URI string describing the entire contents
* of the Intent.
*/
@@ -5133,13 +5157,13 @@ public class Intent implements Parcelable {
data = data.substring(i+1);
break;
}
-
+
// No scheme.
break;
}
}
uri.append(data);
-
+
} else if ((flags&URI_INTENT_SCHEME) != 0) {
uri.append("intent:");
}
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 365f269..023c024 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -34,7 +34,7 @@ import android.util.AndroidException;
import android.util.Config;
import android.util.Log;
import android.util.Printer;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
/**
* Structured description of Intent values to be matched. An IntentFilter can
diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java
deleted file mode 100644
index 88dc332..0000000
--- a/core/java/android/content/SyncAdapter.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.content;
-
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.accounts.Account;
-
-/**
- * @hide
- */
-public abstract class SyncAdapter {
- private static final String TAG = "SyncAdapter";
-
- /** Kernel event log tag. Also listed in data/etc/event-log-tags. */
- public static final int LOG_SYNC_DETAILS = 2743;
-
- class Transport extends ISyncAdapter.Stub {
- public void startSync(ISyncContext syncContext, String authority, Account account,
- Bundle extras) throws RemoteException {
- SyncAdapter.this.startSync(new SyncContext(syncContext), account, authority, extras);
- }
-
- public void cancelSync(ISyncContext syncContext) throws RemoteException {
- SyncAdapter.this.cancelSync();
- }
- }
-
- Transport mTransport = new Transport();
-
- /**
- * Get the Transport object.
- */
- public final ISyncAdapter getISyncAdapter()
- {
- return mTransport;
- }
-
- /**
- * Initiate a sync for this account. SyncAdapter-specific parameters may
- * be specified in extras, which is guaranteed to not be null. IPC invocations
- * of this method and cancelSync() are guaranteed to be serialized.
- *
- * @param syncContext the ISyncContext used to indicate the progress of the sync. When
- * the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
- * @param account the account that should be synced
- * @param authority the authority if the sync request
- * @param extras SyncAdapter-specific parameters
- */
- public abstract void startSync(SyncContext syncContext, Account account, String authority,
- Bundle extras);
-
- /**
- * Cancel the most recently initiated sync. Due to race conditions, this may arrive
- * after the ISyncContext.onFinished() for that sync was called. IPC invocations
- * of this method and startSync() are guaranteed to be serialized.
- */
- public abstract void cancelSync();
-}
diff --git a/core/java/android/content/SyncContext.java b/core/java/android/content/SyncContext.java
index 587586d..cc914c0 100644
--- a/core/java/android/content/SyncContext.java
+++ b/core/java/android/content/SyncContext.java
@@ -56,7 +56,9 @@ public class SyncContext {
if (now < mLastHeartbeatSendTime + HEARTBEAT_SEND_INTERVAL_IN_MS) return;
try {
mLastHeartbeatSendTime = now;
- mSyncContext.sendHeartbeat();
+ if (mSyncContext != null) {
+ mSyncContext.sendHeartbeat();
+ }
} catch (RemoteException e) {
// this should never happen
}
@@ -64,13 +66,15 @@ public class SyncContext {
public void onFinished(SyncResult result) {
try {
- mSyncContext.onFinished(result);
+ if (mSyncContext != null) {
+ mSyncContext.onFinished(result);
+ }
} catch (RemoteException e) {
// this should never happen
}
}
public IBinder getSyncContextBinder() {
- return mSyncContext.asBinder();
+ return (mSyncContext == null) ? null : mSyncContext.asBinder();
}
}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index b2d406b..a9c61dc 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -544,6 +544,46 @@ class SyncManager implements OnAccountsUpdateListener {
return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
}
+ private void initializeSyncAdapter(Account account, String authority) {
+ SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type);
+ RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
+ mSyncAdapters.getServiceInfo(syncAdapterType);
+ if (syncAdapterInfo == null) {
+ Log.w(TAG, "can't find a sync adapter for " + syncAdapterType);
+ return;
+ }
+
+ Intent intent = new Intent();
+ intent.setAction("android.content.SyncAdapter");
+ intent.setComponent(syncAdapterInfo.componentName);
+ mContext.bindService(intent, new InitializerServiceConnection(account, authority),
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
+ }
+
+ private class InitializerServiceConnection implements ServiceConnection {
+ private final Account mAccount;
+ private final String mAuthority;
+
+ public InitializerServiceConnection(Account account, String authority) {
+ mAccount = account;
+ mAuthority = authority;
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority);
+ } catch (RemoteException e) {
+ // doesn't matter, we will retry again later
+ } finally {
+ mContext.unbindService(this);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mContext.unbindService(this);
+ }
+ }
+
/**
* Returns whether or not sync is enabled. Sync can be enabled by
* setting the system property "ro.config.sync" to the value "yes".
@@ -686,36 +726,34 @@ class SyncManager implements OnAccountsUpdateListener {
continue;
}
- // make this an initialization sync if the isSyncable state is unknown
- Bundle extrasCopy = extras;
- long delayCopy = delay;
+ // initialize the SyncAdapter if the isSyncable state is unknown
if (isSyncable < 0) {
- extrasCopy = new Bundle(extras);
- extrasCopy.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
- delayCopy = -1; // expedite this
- } else {
- final boolean syncAutomatically = masterSyncAutomatically
- && mSyncStorageEngine.getSyncAutomatically(account, authority);
- boolean syncAllowed =
- manualSync || (backgroundDataUsageAllowed && syncAutomatically);
- if (!syncAllowed) {
- if (isLoggable) {
- Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
- + " is not allowed, dropping request");
- }
- continue;
+ initializeSyncAdapter(account, authority);
+ continue;
+ }
+
+ final boolean syncAutomatically = masterSyncAutomatically
+ && mSyncStorageEngine.getSyncAutomatically(account, authority);
+ boolean syncAllowed =
+ manualSync || (backgroundDataUsageAllowed && syncAutomatically);
+ if (!syncAllowed) {
+ if (isLoggable) {
+ Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
+ + " is not allowed, dropping request");
}
+ continue;
}
+
if (isLoggable) {
Log.v(TAG, "scheduleSync:"
- + " delay " + delayCopy
+ + " delay " + delay
+ ", source " + source
+ ", account " + account
+ ", authority " + authority
- + ", extras " + extrasCopy);
+ + ", extras " + extras);
}
scheduleSyncOperation(
- new SyncOperation(account, source, authority, extrasCopy, delayCopy));
+ new SyncOperation(account, source, authority, extras, delay));
}
}
}
@@ -821,8 +859,8 @@ class SyncManager implements OnAccountsUpdateListener {
}
// Cap the delay
- long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContext.getContentResolver(),
- Settings.Gservices.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+ long maxSyncRetryTimeInSeconds = Settings.Secure.getLong(mContext.getContentResolver(),
+ Settings.Secure.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
@@ -1107,7 +1145,8 @@ class SyncManager implements OnAccountsUpdateListener {
com.android.internal.R.string.sync_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
- return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ return mContext.bindService(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
}
void unBindFromSyncAdapter() {
diff --git a/core/java/android/content/SyncStateContentProviderHelper.java b/core/java/android/content/SyncStateContentProviderHelper.java
deleted file mode 100644
index 64bbe25..0000000
--- a/core/java/android/content/SyncStateContentProviderHelper.java
+++ /dev/null
@@ -1,243 +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.content;
-
-import com.android.internal.util.ArrayUtils;
-
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.net.Uri;
-import android.accounts.Account;
-
-/**
- * Extends the schema of a ContentProvider to include the _sync_state table
- * and implements query/insert/update/delete to access that table using the
- * authority "syncstate". This can be used to store the sync state for a
- * set of accounts.
- *
- * @hide
- */
-public class SyncStateContentProviderHelper {
- final SQLiteOpenHelper mOpenHelper;
-
- private static final String SYNC_STATE_AUTHORITY = "syncstate";
- private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
- private static final int STATE = 0;
-
- private static final Uri CONTENT_URI =
- Uri.parse("content://" + SYNC_STATE_AUTHORITY + "/state");
-
- private static final String ACCOUNT_WHERE = "_sync_account = ? AND _sync_account_type = ?";
-
- private final Provider mInternalProviderInterface;
-
- private static final String SYNC_STATE_TABLE = "_sync_state";
- private static long DB_VERSION = 3;
-
- private static final String[] ACCOUNT_PROJECTION =
- new String[]{"_sync_account", "_sync_account_type"};
-
- static {
- sURIMatcher.addURI(SYNC_STATE_AUTHORITY, "state", STATE);
- }
-
- public SyncStateContentProviderHelper(SQLiteOpenHelper openHelper) {
- mOpenHelper = openHelper;
- mInternalProviderInterface = new Provider();
- }
-
- public ContentProvider asContentProvider() {
- return mInternalProviderInterface;
- }
-
- public void createDatabase(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS _sync_state");
- db.execSQL("CREATE TABLE _sync_state (" +
- "_id INTEGER PRIMARY KEY," +
- "_sync_account TEXT," +
- "_sync_account_type TEXT," +
- "data TEXT," +
- "UNIQUE(_sync_account, _sync_account_type)" +
- ");");
-
- db.execSQL("DROP TABLE IF EXISTS _sync_state_metadata");
- db.execSQL("CREATE TABLE _sync_state_metadata (" +
- "version INTEGER" +
- ");");
- ContentValues values = new ContentValues();
- values.put("version", DB_VERSION);
- db.insert("_sync_state_metadata", "version", values);
- }
-
- protected void onDatabaseOpened(SQLiteDatabase db) {
- long version = DatabaseUtils.longForQuery(db,
- "select version from _sync_state_metadata", null);
- if (version != DB_VERSION) {
- createDatabase(db);
- }
- }
-
- class Provider extends ContentProvider {
- public boolean onCreate() {
- throw new UnsupportedOperationException("not implemented");
- }
-
- public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- int match = sURIMatcher.match(url);
- switch (match) {
- case STATE:
- return db.query(SYNC_STATE_TABLE, projection, selection, selectionArgs,
- null, null, sortOrder);
- default:
- throw new UnsupportedOperationException("Cannot query URL: " + url);
- }
- }
-
- public String getType(Uri uri) {
- throw new UnsupportedOperationException("not implemented");
- }
-
- public Uri insert(Uri url, ContentValues values) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- int match = sURIMatcher.match(url);
- switch (match) {
- case STATE: {
- long id = db.insert(SYNC_STATE_TABLE, "feed", values);
- return CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build();
- }
- default:
- throw new UnsupportedOperationException("Cannot insert into URL: " + url);
- }
- }
-
- public int delete(Uri url, String userWhere, String[] whereArgs) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- switch (sURIMatcher.match(url)) {
- case STATE:
- return db.delete(SYNC_STATE_TABLE, userWhere, whereArgs);
- default:
- throw new IllegalArgumentException("Unknown URL " + url);
- }
-
- }
-
- public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- switch (sURIMatcher.match(url)) {
- case STATE:
- return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs);
- default:
- throw new UnsupportedOperationException("Cannot update URL: " + url);
- }
-
- }
- }
-
- /**
- * Check if the url matches content that this ContentProvider manages.
- * @param url the Uri to check
- * @return true if this ContentProvider can handle that Uri.
- */
- public boolean matches(Uri url) {
- return (SYNC_STATE_AUTHORITY.equals(url.getAuthority()));
- }
-
- /**
- * Replaces the contents of the _sync_state table in the destination ContentProvider
- * with the row that matches account, if any, in the source ContentProvider.
- * <p>
- * The ContentProviders must expose the _sync_state table as URI content://syncstate/state.
- * @param dbSrc the database to read from
- * @param dbDest the database to write to
- * @param account the account of the row that should be copied over.
- */
- public void copySyncState(SQLiteDatabase dbSrc, SQLiteDatabase dbDest,
- Account account) {
- final String[] whereArgs = new String[]{account.name, account.type};
- Cursor c = dbSrc.query(SYNC_STATE_TABLE,
- new String[]{"_sync_account", "_sync_account_type", "data"},
- ACCOUNT_WHERE, whereArgs, null, null, null);
- try {
- if (c.moveToNext()) {
- ContentValues values = new ContentValues();
- values.put("_sync_account", c.getString(0));
- values.put("_sync_account_type", c.getString(1));
- values.put("data", c.getBlob(2));
- dbDest.replace(SYNC_STATE_TABLE, "_sync_account", values);
- }
- } finally {
- c.close();
- }
- }
-
- public void onAccountsChanged(Account[] accounts) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null);
- try {
- while (c.moveToNext()) {
- final String accountName = c.getString(0);
- final String accountType = c.getString(1);
- Account account = new Account(accountName, accountType);
- if (!ArrayUtils.contains(accounts, account)) {
- db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE,
- new String[]{accountName, accountType});
- }
- }
- } finally {
- c.close();
- }
- }
-
- public void discardSyncData(SQLiteDatabase db, Account account) {
- if (account != null) {
- db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account.name, account.type});
- } else {
- db.delete(SYNC_STATE_TABLE, null, null);
- }
- }
-
- /**
- * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
- */
- public byte[] readSyncDataBytes(SQLiteDatabase db, Account account) {
- Cursor c = db.query(SYNC_STATE_TABLE, null, ACCOUNT_WHERE,
- new String[]{account.name, account.type}, null, null, null);
- try {
- if (c.moveToFirst()) {
- return c.getBlob(c.getColumnIndexOrThrow("data"));
- }
- } finally {
- c.close();
- }
- return null;
- }
-
- /**
- * Sets the SyncData bytes for the given account. The bytes array may be null.
- */
- public void writeSyncDataBytes(SQLiteDatabase db, Account account, byte[] data) {
- ContentValues values = new ContentValues();
- values.put("data", data);
- db.update(SYNC_STATE_TABLE, values, ACCOUNT_WHERE,
- new String[]{account.name, account.type});
- }
-}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index be70909..4c53201 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -18,14 +18,13 @@ package android.content;
import com.android.internal.os.AtomicFile;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastXmlSerializer;
+import com.android.common.FastXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.accounts.Account;
-import android.backup.IBackupManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -37,7 +36,7 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -258,7 +257,9 @@ public class SyncStorageEngine extends Handler {
mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
- File dataDir = Environment.getDataDirectory();
+ // This call will return the correct directory whether Encrypted File Systems is
+ // enabled or not.
+ File dataDir = Environment.getSecureDataDirectory();
File systemDir = new File(dataDir, "system");
File syncDir = new File(systemDir, "sync");
mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
@@ -511,7 +512,7 @@ public class SyncStorageEngine extends Handler {
SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
status.pending = true;
- status.initialize = op.extras != null &&
+ status.initialize = op.extras != null &&
op.extras.containsKey(ContentResolver.SYNC_EXTRAS_INITIALIZE) &&
op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE);
}
diff --git a/core/java/android/content/SyncableContentProvider.java b/core/java/android/content/SyncableContentProvider.java
deleted file mode 100644
index ab4e91c..0000000
--- a/core/java/android/content/SyncableContentProvider.java
+++ /dev/null
@@ -1,237 +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.content;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.accounts.Account;
-
-import java.util.Map;
-
-/**
- * A specialization of the ContentProvider that centralizes functionality
- * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
- * inside of database transactions.
- *
- * @hide
- */
-public abstract class SyncableContentProvider extends ContentProvider {
- protected abstract boolean isTemporary();
-
- private volatile TempProviderSyncAdapter mTempProviderSyncAdapter;
-
- public void setTempProviderSyncAdapter(TempProviderSyncAdapter syncAdapter) {
- mTempProviderSyncAdapter = syncAdapter;
- }
-
- public TempProviderSyncAdapter getTempProviderSyncAdapter() {
- return mTempProviderSyncAdapter;
- }
-
- /**
- * Close resources that must be closed. You must call this to properly release
- * the resources used by the SyncableContentProvider.
- */
- public abstract void close();
-
- /**
- * Override to create your schema and do anything else you need to do with a new database.
- * This is run inside a transaction (so you don't need to use one).
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- */
- protected abstract void bootstrapDatabase(SQLiteDatabase db);
-
- /**
- * Override to upgrade your database from an old version to the version you specified.
- * Don't set the DB version, this will automatically be done after the method returns.
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- *
- * @param oldVersion version of the existing database
- * @param newVersion current version to upgrade to
- * @return true if the upgrade was lossless, false if it was lossy
- */
- protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
-
- /**
- * Override to do anything (like cleanups or checks) you need to do after opening a database.
- * Does nothing by default. This is run inside a transaction (so you don't need to use one).
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- */
- protected abstract void onDatabaseOpened(SQLiteDatabase db);
-
- /**
- * Get a non-persistent instance of this content provider.
- * You must call {@link #close} on the returned
- * SyncableContentProvider when you are done with it.
- *
- * @return a non-persistent content provider with the same layout as this
- * provider.
- */
- public abstract SyncableContentProvider getTemporaryInstance();
-
- public abstract SQLiteDatabase getDatabase();
-
- public abstract boolean getContainsDiffs();
-
- public abstract void setContainsDiffs(boolean containsDiffs);
-
- /**
- * Each subclass of this class should define a subclass of {@link
- * AbstractTableMerger} for each table they wish to merge. It
- * should then override this method and return one instance of
- * each merger, in sequence. Their {@link
- * AbstractTableMerger#merge merge} methods will be called, one at a
- * time, in the order supplied.
- *
- * <p>The default implementation returns an empty list, so that no
- * merging will occur.
- * @return A sequence of subclasses of {@link
- * AbstractTableMerger}, one for each table that should be merged.
- */
- protected abstract Iterable<? extends AbstractTableMerger> getMergers();
-
- /**
- * Check if changes to this URI can be syncable changes.
- * @param uri the URI of the resource that was changed
- * @return true if changes to this URI can be syncable changes, false otherwise
- */
- public abstract boolean changeRequiresLocalSync(Uri uri);
-
- /**
- * Called right before a sync is started.
- *
- * @param context the sync context for the operation
- * @param account
- */
- public abstract void onSyncStart(SyncContext context, Account account);
-
- /**
- * Called right after a sync is completed
- *
- * @param context the sync context for the operation
- * @param success true if the sync succeeded, false if an error occurred
- */
- public abstract void onSyncStop(SyncContext context, boolean success);
-
- /**
- * The account of the most recent call to onSyncStart()
- * @return the account
- */
- public abstract Account getSyncingAccount();
-
- /**
- * Merge diffs from a sync source with this content provider.
- *
- * @param context the SyncContext within which this merge is taking place
- * @param diffs A temporary content provider containing diffs from a sync
- * source.
- * @param result a MergeResult that contains information about the merge, including
- * a temporary content provider with the same layout as this provider containing
- * @param syncResult
- */
- public abstract void merge(SyncContext context, SyncableContentProvider diffs,
- TempProviderSyncResult result, SyncResult syncResult);
-
-
- /**
- * Invoked when the active sync has been canceled. The default
- * implementation doesn't do anything (except ensure that this
- * provider is syncable). Subclasses of ContentProvider
- * that support canceling of sync should override this.
- */
- public abstract void onSyncCanceled();
-
-
- public abstract boolean isMergeCancelled();
-
- /**
- * Subclasses should override this instead of update(). See update()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract int updateInternal(Uri url, ContentValues values,
- String selection, String[] selectionArgs);
-
- /**
- * Subclasses should override this instead of delete(). See delete()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
-
- /**
- * Subclasses should override this instead of insert(). See insert()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract Uri insertInternal(Uri url, ContentValues values);
-
- /**
- * Subclasses should override this instead of query(). See query()
- * for details.
- *
- * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
- * block for performance reasons. If an implementation needs atomic access
- * to the database the lock can be acquired then.
- */
- protected abstract Cursor queryInternal(Uri url, String[] projection,
- String selection, String[] selectionArgs, String sortOrder);
-
- /**
- * Make sure that there are no entries for accounts that no longer exist
- * @param accountsArray the array of currently-existing accounts
- */
- protected abstract void onAccountsChanged(Account[] accountsArray);
-
- /**
- * A helper method to delete all rows whose account is not in the accounts
- * map. The accountColumnName is the name of the column that is expected
- * to hold the account. If a row has an empty account it is never deleted.
- *
- * @param accounts a map of existing accounts
- * @param table the table to delete from
- */
- protected abstract void deleteRowsForRemovedAccounts(Map<Account, Boolean> accounts,
- String table);
-
- /**
- * Called when the sync system determines that this provider should no longer
- * contain records for the specified account.
- */
- public abstract void wipeAccount(Account account);
-
- /**
- * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
- */
- public abstract byte[] readSyncDataBytes(Account account);
-
- /**
- * Sets the SyncData bytes for the given account. The bytes array may be null.
- */
- public abstract void writeSyncDataBytes(Account account, byte[] data);
-}
-
diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java
deleted file mode 100644
index b46c545..0000000
--- a/core/java/android/content/TempProviderSyncAdapter.java
+++ /dev/null
@@ -1,585 +0,0 @@
-package android.content;
-
-import android.database.SQLException;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.NetStat;
-import android.os.Parcelable;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.TimingLogger;
-import android.accounts.Account;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-
-import java.io.IOException;
-
-/**
- * @hide
- */
-public abstract class TempProviderSyncAdapter extends SyncAdapter {
- private static final String TAG = "Sync";
-
- private static final int MAX_GET_SERVER_DIFFS_LOOP_COUNT = 20;
- private static final int MAX_UPLOAD_CHANGES_LOOP_COUNT = 10;
- private static final int NUM_ALLOWED_SIMULTANEOUS_DELETIONS = 5;
- private static final long PERCENT_ALLOWED_SIMULTANEOUS_DELETIONS = 20;
-
- private volatile SyncableContentProvider mProvider;
- private volatile SyncThread mSyncThread = null;
- private volatile boolean mProviderSyncStarted;
- private volatile boolean mAdapterSyncStarted;
-
- public TempProviderSyncAdapter(SyncableContentProvider provider) {
- super();
- mProvider = provider;
- }
-
- /**
- * Used by getServerDiffs() to track the sync progress for a given
- * sync adapter. Implementations of SyncAdapter generally specialize
- * this class in order to track specific data about that SyncAdapter's
- * sync. If an implementation of SyncAdapter doesn't need to store
- * any data for a sync it may use TrivialSyncData.
- */
- public static abstract class SyncData implements Parcelable {
-
- }
-
- public final void setContext(Context context) {
- mContext = context;
- }
-
- /**
- * Retrieve the Context this adapter is running in. Only available
- * once onSyncStarting() is called (not available from constructor).
- */
- final public Context getContext() {
- return mContext;
- }
-
- /**
- * Called right before a sync is started.
- *
- * @param context allows you to publish status and interact with the
- * @param account the account to sync
- * @param manualSync true if this sync was requested manually by the user
- * @param result information to track what happened during this sync attempt
- */
- public abstract void onSyncStarting(SyncContext context, Account account, boolean manualSync,
- SyncResult result);
-
- /**
- * Called right after a sync is completed
- *
- * @param context allows you to publish status and interact with the
- * user during interactive syncs.
- * @param success true if the sync suceeded, false if an error occured
- */
- public abstract void onSyncEnding(SyncContext context, boolean success);
-
- /**
- * Implement this to return true if the data in your content provider
- * is read only.
- */
- public abstract boolean isReadOnly();
-
- public abstract boolean getIsSyncable(Account account)
- throws IOException, AuthenticatorException, OperationCanceledException;
-
- /**
- * Get diffs from the server since the last completed sync and put them
- * into a temporary provider.
- *
- * @param context allows you to publish status and interact with the
- * user during interactive syncs.
- * @param syncData used to track the progress this client has made in syncing data
- * from the server
- * @param tempProvider this is where the diffs should be stored
- * @param extras any extra data describing the sync that is desired
- * @param syncInfo sync adapter-specific data that is used during a single sync operation
- * @param syncResult information to track what happened during this sync attempt
- */
- public abstract void getServerDiffs(SyncContext context,
- SyncData syncData, SyncableContentProvider tempProvider,
- Bundle extras, Object syncInfo, SyncResult syncResult);
-
- /**
- * Send client diffs to the server, optionally receiving more diffs from the server
- *
- * @param context allows you to publish status and interact with the
- * user during interactive syncs.
- * @param clientDiffs the diffs from the client
- * @param serverDiffs the SyncableContentProvider that should be populated with
-* the entries that were returned in response to an insert/update/delete request
-* to the server
- * @param syncResult information to track what happened during this sync attempt
- * @param dontActuallySendDeletes
- */
- public abstract void sendClientDiffs(SyncContext context,
- SyncableContentProvider clientDiffs,
- SyncableContentProvider serverDiffs, SyncResult syncResult,
- boolean dontActuallySendDeletes);
-
- /**
- * Reads the sync data from the ContentProvider
- * @param contentProvider the ContentProvider to read from
- * @return the SyncData for the provider. This may be null.
- */
- public SyncData readSyncData(SyncableContentProvider contentProvider) {
- return null;
- }
-
- /**
- * Create and return a new, empty SyncData object
- */
- public SyncData newSyncData() {
- return null;
- }
-
- /**
- * Stores the sync data in the Sync Stats database, keying it by
- * the account that was set in the last call to onSyncStarting()
- */
- public void writeSyncData(SyncData syncData, SyncableContentProvider contentProvider) {}
-
- /**
- * Indicate to the SyncAdapter that the last sync that was started has
- * been cancelled.
- */
- public abstract void onSyncCanceled();
-
- /**
- * Initializes the temporary content providers used during
- * {@link TempProviderSyncAdapter#sendClientDiffs}.
- * May copy relevant data from the underlying db into this provider so
- * joins, etc., can work.
- *
- * @param cp The ContentProvider to initialize.
- */
- protected void initTempProvider(SyncableContentProvider cp) {}
-
- protected Object createSyncInfo() {
- return null;
- }
-
- /**
- * Called when the accounts list possibly changed, to give the
- * SyncAdapter a chance to do any necessary bookkeeping, e.g.
- * to make sure that any required SubscribedFeeds subscriptions
- * exist.
- * @param accounts the list of accounts
- */
- public abstract void onAccountsChanged(Account[] accounts);
-
- private Context mContext;
-
- private class SyncThread extends Thread {
- private final Account mAccount;
- private final String mAuthority;
- private final Bundle mExtras;
- private final SyncContext mSyncContext;
- private volatile boolean mIsCanceled = false;
- private long mInitialTxBytes;
- private long mInitialRxBytes;
- private final SyncResult mResult;
-
- SyncThread(SyncContext syncContext, Account account, String authority, Bundle extras) {
- super("SyncThread");
- mAccount = account;
- mAuthority = authority;
- mExtras = extras;
- mSyncContext = syncContext;
- mResult = new SyncResult();
- }
-
- void cancelSync() {
- mIsCanceled = true;
- if (mAdapterSyncStarted) onSyncCanceled();
- if (mProviderSyncStarted) mProvider.onSyncCanceled();
- // We may lose the last few sync events when canceling. Oh well.
- int uid = Process.myUid();
- logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
- }
-
- @Override
- public void run() {
- Process.setThreadPriority(Process.myTid(),
- Process.THREAD_PRIORITY_BACKGROUND);
- int uid = Process.myUid();
- mInitialTxBytes = NetStat.getUidTxBytes(uid);
- mInitialRxBytes = NetStat.getUidRxBytes(uid);
- try {
- sync(mSyncContext, mAccount, mAuthority, mExtras);
- } catch (SQLException e) {
- Log.e(TAG, "Sync failed", e);
- mResult.databaseError = true;
- } finally {
- mSyncThread = null;
- if (!mIsCanceled) {
- logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
- mSyncContext.onFinished(mResult);
- }
- }
- }
-
- private void sync(SyncContext syncContext, Account account, String authority,
- Bundle extras) {
- mIsCanceled = false;
-
- mProviderSyncStarted = false;
- mAdapterSyncStarted = false;
- String message = null;
-
- // always attempt to initialize if the isSyncable state isn't set yet
- int isSyncable = ContentResolver.getIsSyncable(account, authority);
- if (isSyncable < 0) {
- try {
- isSyncable = (getIsSyncable(account)) ? 1 : 0;
- ContentResolver.setIsSyncable(account, authority, isSyncable);
- } catch (IOException e) {
- ++mResult.stats.numIoExceptions;
- } catch (AuthenticatorException e) {
- ++mResult.stats.numParseExceptions;
- } catch (OperationCanceledException e) {
- // do nothing
- }
- }
-
- // if this is an initialization request then our work is done here
- if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
- return;
- }
-
- // if we aren't syncable then get out
- if (isSyncable <= 0) {
- return;
- }
-
- boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
-
- try {
- mProvider.onSyncStart(syncContext, account);
- mProviderSyncStarted = true;
- onSyncStarting(syncContext, account, manualSync, mResult);
- if (mResult.hasError()) {
- message = "SyncAdapter failed while trying to start sync";
- return;
- }
- mAdapterSyncStarted = true;
- if (mIsCanceled) {
- return;
- }
- final String syncTracingEnabledValue = SystemProperties.get(TAG + "Tracing");
- final boolean syncTracingEnabled = !TextUtils.isEmpty(syncTracingEnabledValue);
- try {
- if (syncTracingEnabled) {
- System.gc();
- System.gc();
- Debug.startMethodTracing("synctrace." + System.currentTimeMillis());
- }
- runSyncLoop(syncContext, account, extras);
- } finally {
- if (syncTracingEnabled) Debug.stopMethodTracing();
- }
- onSyncEnding(syncContext, !mResult.hasError());
- mAdapterSyncStarted = false;
- mProvider.onSyncStop(syncContext, true);
- mProviderSyncStarted = false;
- } finally {
- if (mAdapterSyncStarted) {
- mAdapterSyncStarted = false;
- onSyncEnding(syncContext, false);
- }
- if (mProviderSyncStarted) {
- mProviderSyncStarted = false;
- mProvider.onSyncStop(syncContext, false);
- }
- if (!mIsCanceled) {
- if (message != null) syncContext.setStatusText(message);
- }
- }
- }
-
- private void runSyncLoop(SyncContext syncContext, Account account, Bundle extras) {
- TimingLogger syncTimer = new TimingLogger(TAG + "Profiling", "sync");
- syncTimer.addSplit("start");
- int loopCount = 0;
- boolean tooManyGetServerDiffsAttempts = false;
-
- final boolean overrideTooManyDeletions =
- extras.getBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS,
- false);
- final boolean discardLocalDeletions =
- extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, false);
- boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD,
- false /* default this flag to false */);
- SyncableContentProvider serverDiffs = null;
- TempProviderSyncResult result = new TempProviderSyncResult();
- try {
- if (!uploadOnly) {
- /**
- * This loop repeatedly calls SyncAdapter.getServerDiffs()
- * (to get changes from the feed) followed by
- * ContentProvider.merge() (to incorporate these changes
- * into the provider), stopping when the SyncData returned
- * from getServerDiffs() indicates that all the data was
- * fetched.
- */
- while (!mIsCanceled) {
- // Don't let a bad sync go forever
- if (loopCount++ == MAX_GET_SERVER_DIFFS_LOOP_COUNT) {
- Log.e(TAG, "runSyncLoop: Hit max loop count while getting server diffs "
- + getClass().getName());
- // TODO: change the structure here to schedule a new sync
- // with a backoff time, keeping track to be sure
- // we don't keep doing this forever (due to some bug or
- // mismatch between the client and the server)
- tooManyGetServerDiffsAttempts = true;
- break;
- }
-
- // Get an empty content provider to put the diffs into
- if (serverDiffs != null) serverDiffs.close();
- serverDiffs = mProvider.getTemporaryInstance();
-
- // Get records from the server which will be put into the serverDiffs
- initTempProvider(serverDiffs);
- Object syncInfo = createSyncInfo();
- SyncData syncData = readSyncData(serverDiffs);
- // syncData will only be null if there was a demarshalling error
- // while reading the sync data.
- if (syncData == null) {
- mProvider.wipeAccount(account);
- syncData = newSyncData();
- }
- mResult.clear();
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: running getServerDiffs using syncData "
- + syncData.toString());
- }
- getServerDiffs(syncContext, syncData, serverDiffs, extras, syncInfo,
- mResult);
-
- if (mIsCanceled) return;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: result: " + mResult);
- }
- if (mResult.hasError()) return;
- if (mResult.partialSyncUnavailable) {
- if (Config.LOGD) {
- Log.d(TAG, "partialSyncUnavailable is set, setting "
- + "ignoreSyncData and retrying");
- }
- mProvider.wipeAccount(account);
- continue;
- }
-
- // write the updated syncData back into the temp provider
- writeSyncData(syncData, serverDiffs);
-
- // apply the downloaded changes to the provider
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: running merge");
- }
- mProvider.merge(syncContext, serverDiffs,
- null /* don't return client diffs */, mResult);
- if (mIsCanceled) return;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: result: " + mResult);
- }
-
- // if the server has no more changes then break out of the loop
- if (!mResult.moreRecordsToGet) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: fetched all data, moving on");
- }
- break;
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: more data to fetch, looping");
- }
- }
- }
-
- /**
- * This loop repeatedly calls ContentProvider.merge() followed
- * by SyncAdapter.merge() until either indicate that there is
- * no more work to do by returning null.
- * <p>
- * The initial ContentProvider.merge() returns a temporary
- * ContentProvider that contains any local changes that need
- * to be committed to the server.
- * <p>
- * The SyncAdapter.merge() calls upload the changes to the server
- * and populates temporary provider (the serverDiffs) with the
- * result.
- * <p>
- * Subsequent calls to ContentProvider.merge() incoporate the
- * result of previous SyncAdapter.merge() calls into the
- * real ContentProvider and again return a temporary
- * ContentProvider that contains any local changes that need
- * to be committed to the server.
- */
- loopCount = 0;
- boolean readOnly = isReadOnly();
- long previousNumModifications = 0;
- if (serverDiffs != null) {
- serverDiffs.close();
- serverDiffs = null;
- }
-
- // If we are discarding local deletions then we need to redownload all the items
- // again (since some of them might have been deleted). We do this by deleting the
- // sync data for the current account by writing in a null one.
- if (discardLocalDeletions) {
- serverDiffs = mProvider.getTemporaryInstance();
- initTempProvider(serverDiffs);
- writeSyncData(null, serverDiffs);
- }
-
- while (!mIsCanceled) {
- if (Config.LOGV) {
- Log.v(TAG, "runSyncLoop: Merging diffs from server to client");
- }
- if (result.tempContentProvider != null) {
- result.tempContentProvider.close();
- result.tempContentProvider = null;
- }
- mResult.clear();
- mProvider.merge(syncContext, serverDiffs, readOnly ? null : result,
- mResult);
- if (mIsCanceled) return;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: result: " + mResult);
- }
-
- SyncableContentProvider clientDiffs =
- readOnly ? null : result.tempContentProvider;
- if (clientDiffs == null) {
- // Nothing to commit back to the server
- if (Config.LOGV) Log.v(TAG, "runSyncLoop: No client diffs");
- break;
- }
-
- long numModifications = mResult.stats.numUpdates
- + mResult.stats.numDeletes
- + mResult.stats.numInserts;
-
- // as long as we are making progress keep resetting the loop count
- if (numModifications < previousNumModifications) {
- loopCount = 0;
- }
- previousNumModifications = numModifications;
-
- // Don't let a bad sync go forever
- if (loopCount++ >= MAX_UPLOAD_CHANGES_LOOP_COUNT) {
- Log.e(TAG, "runSyncLoop: Hit max loop count while syncing "
- + getClass().getName());
- mResult.tooManyRetries = true;
- break;
- }
-
- if (!overrideTooManyDeletions && !discardLocalDeletions
- && hasTooManyDeletions(mResult.stats)) {
- if (Config.LOGD) {
- Log.d(TAG, "runSyncLoop: Too many deletions were found in provider "
- + getClass().getName() + ", not doing any more updates");
- }
- long numDeletes = mResult.stats.numDeletes;
- mResult.stats.clear();
- mResult.tooManyDeletions = true;
- mResult.stats.numDeletes = numDeletes;
- break;
- }
-
- if (Config.LOGV) Log.v(TAG, "runSyncLoop: Merging diffs from client to server");
- if (serverDiffs != null) serverDiffs.close();
- serverDiffs = clientDiffs.getTemporaryInstance();
- initTempProvider(serverDiffs);
- mResult.clear();
- sendClientDiffs(syncContext, clientDiffs, serverDiffs, mResult,
- discardLocalDeletions);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: result: " + mResult);
- }
-
- if (!mResult.madeSomeProgress()) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: No data from client diffs merge");
- }
- break;
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: made some progress, looping");
- }
- }
-
- // add in any status codes that we saved from earlier
- mResult.tooManyRetries |= tooManyGetServerDiffsAttempts;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: final result: " + mResult);
- }
- } finally {
- // do this in the finally block to guarantee that is is set and not overwritten
- if (discardLocalDeletions) {
- mResult.fullSyncRequested = true;
- }
- if (serverDiffs != null) serverDiffs.close();
- if (result.tempContentProvider != null) result.tempContentProvider.close();
- syncTimer.addSplit("stop");
- syncTimer.dumpToLog();
- }
- }
- }
-
- /**
- * Logs details on the sync.
- * Normally this will be overridden by a subclass that will provide
- * provider-specific details.
- *
- * @param bytesSent number of bytes the sync sent over the network
- * @param bytesReceived number of bytes the sync received over the network
- * @param result The SyncResult object holding info on the sync
- */
- protected void logSyncDetails(long bytesSent, long bytesReceived, SyncResult result) {
- EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, "");
- }
-
- public void startSync(SyncContext syncContext, Account account, String authority,
- Bundle extras) {
- if (mSyncThread != null) {
- syncContext.onFinished(SyncResult.ALREADY_IN_PROGRESS);
- return;
- }
-
- mSyncThread = new SyncThread(syncContext, account, authority, extras);
- mSyncThread.start();
- }
-
- public void cancelSync() {
- if (mSyncThread != null) {
- mSyncThread.cancelSync();
- }
- }
-
- protected boolean hasTooManyDeletions(SyncStats stats) {
- long numEntries = stats.numEntries;
- long numDeletedEntries = stats.numDeletes;
-
- long percentDeleted = (numDeletedEntries == 0)
- ? 0
- : (100 * numDeletedEntries /
- (numEntries + numDeletedEntries));
- boolean tooManyDeletions =
- (numDeletedEntries > NUM_ALLOWED_SIMULTANEOUS_DELETIONS)
- && (percentDeleted > PERCENT_ALLOWED_SIMULTANEOUS_DELETIONS);
- return tooManyDeletions;
- }
-}
diff --git a/core/java/android/content/TempProviderSyncResult.java b/core/java/android/content/TempProviderSyncResult.java
deleted file mode 100644
index 81f6f79..0000000
--- a/core/java/android/content/TempProviderSyncResult.java
+++ /dev/null
@@ -1,36 +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.content;
-
-/**
- * Used to hold data returned from a given phase of a TempProviderSync.
- * @hide
- */
-public class TempProviderSyncResult {
- /**
- * An interface to a temporary content provider that contains
- * the result of updates that were sent to the server. This
- * provider must be merged into the permanent content provider.
- * This may be null, which indicates that there is nothing to
- * merge back into the content provider.
- */
- public SyncableContentProvider tempContentProvider;
-
- public TempProviderSyncResult() {
- tempContentProvider = null;
- }
-}
diff --git a/core/java/android/content/package.html b/core/java/android/content/package.html
index dd5360f..eac679d 100644
--- a/core/java/android/content/package.html
+++ b/core/java/android/content/package.html
@@ -421,7 +421,7 @@ can supply them explicitly in the XML file:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
<b>android:textSize="18" android:textColor="#008"</b>
android:text="Hello, World!" /&gt;
&lt;/root&gt;
@@ -447,7 +447,7 @@ one of those resources:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
<b>android:textColor="@color/opaque_red"</b>
android:text="Hello, World!" /&gt;
&lt;/root&gt;
@@ -463,7 +463,7 @@ reference a system resource, you would need to write:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
android:textColor="@<b>android:</b>color/opaque_red"
android:text="Hello, World!" /&gt;
&lt;/root&gt;
@@ -476,7 +476,7 @@ strings in a layout file so that they can be localized:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
android:textColor="@android:color/opaque_red"
android:text="@string/hello_world" /&gt;
&lt;/root&gt;
@@ -509,7 +509,7 @@ one of the standard colors defined in the base system theme:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
<b>android:textColor="?android:textDisabledColor"</b>
android:text="@string/hello_world" /&gt;
&lt;/root&gt;
@@ -637,10 +637,10 @@ new style resource with the desired values:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text1" <b>style="@style/SpecialText"</b>
- android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:layout_width="match_parent" android:layout_height="wrap_content"
android:text="Hello, World!" /&gt;
&lt;EditText id="text2" <b>style="@style/SpecialText"</b>
- android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:layout_width="match_parent" android:layout_height="wrap_content"
android:text="I love you all." /&gt;
&lt;/root&gt;</pre>
<h4>&nbsp;</h4>
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1800c30..3dea286 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -208,6 +208,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_RESTORE_NEEDS_APPLICATION = 1<<16;
/**
+ * Value for {@link #flags}: this is true if the application has set
+ * its android:neverEncrypt to true, false otherwise. It is used to specify
+ * that this package specifically "opts-out" of a secured file system solution,
+ * and will always store its data in-the-clear.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_NEVER_ENCRYPT = 1<<17;
+
+ /**
* Flags associated with the application. Any combination of
* {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3f8c71e..ad99f54 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -35,7 +35,7 @@ import android.util.Config;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import java.io.File;
import java.io.IOException;
@@ -1399,6 +1399,12 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_TEST_ONLY;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_neverEncrypt,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_NEVER_ENCRYPT;
+ }
+
String str;
str = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestApplication_permission);
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index b39a67d..b819fa0 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -43,7 +43,7 @@ import java.io.IOException;
import java.io.FileInputStream;
import com.android.internal.os.AtomicFile;
-import com.android.internal.util.FastXmlSerializer;
+import com.android.common.FastXmlSerializer;
import com.google.android.collect.Maps;
import com.google.android.collect.Lists;
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 453a83d..70baaef 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -44,7 +44,7 @@ import java.util.Arrays;
* &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
* &lt;item android:state_focused="true" android:color="@color/testcolor1"/&gt;
* &lt;item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /&gt;
- * &lt;item android:state_enabled="false" android:colore="@color/testcolor3" /&gt;
+ * &lt;item android:state_enabled="false" android:color="@color/testcolor3" /&gt;
* &lt;item android:state_active="true" android:color="@color/testcolor4" /&gt;
* &lt;item android:color="@color/testcolor5"/&gt;
* &lt;/selector&gt;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 1c0ed36..e4fc259 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -17,7 +17,7 @@
package android.content.res;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 8fb82be..2411177 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -24,7 +24,7 @@ import android.util.SparseArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
/**
* Conveniences for retrieving data out of a compiled string resource.
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 016ee7f..8f0003b 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -5,7 +5,7 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import java.util.Arrays;
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 6336678..f800232 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -17,7 +17,7 @@
package android.content.res;
import android.util.TypedValue;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 4ca6601..9bfbb74 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -671,6 +671,102 @@ public class DatabaseUtils {
}
/**
+ * Reads a String out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getString(index));
+ }
+ }
+
+ /**
+ * Reads a Long out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getLong(index));
+ }
+ }
+
+ /**
+ * Reads a Short out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getShort(index));
+ }
+ }
+
+ /**
+ * Reads a Integer out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getInt(index));
+ }
+ }
+
+ /**
+ * Reads a Float out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getFloat(index));
+ }
+ }
+
+ /**
+ * Reads a Double out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getDouble(index));
+ }
+ }
+
+ /**
* This class allows users to do multiple inserts into a table but
* compile the SQL insert statement only once, which may increase
* performance.
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
new file mode 100644
index 0000000..79527b4
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.database.sqlite;
+
+import android.util.Log;
+
+/**
+ * This class encapsulates compilation of sql statement and release of the compiled statement obj.
+ * Once a sql statement is compiled, it is cached in {@link SQLiteDatabase}
+ * and it is released in one of the 2 following ways
+ * 1. when {@link SQLiteDatabase} object is closed.
+ * 2. dalvikVM wants to reclaim some memory and releases it from the cache in
+ * {@link SQLiteDatabase}.
+ */
+/* package */ class SQLiteCompiledSql {
+
+ /** The database this program is compiled against. */
+ /* package */ SQLiteDatabase mDatabase;
+
+ /**
+ * Native linkage, do not modify. This comes from the database.
+ */
+ /* package */ int nHandle = 0;
+
+ /**
+ * Native linkage, do not modify. When non-0 this holds a reference to a valid
+ * sqlite3_statement object. It is only updated by the native code, but may be
+ * checked in this class when the database lock is held to determine if there
+ * is a valid native-side program or not.
+ */
+ /* package */ int nStatement = 0;
+
+ /* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
+ mDatabase = db;
+ this.nHandle = db.mNativeHandle;
+ compile(sql, true);
+ }
+
+ /**
+ * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
+ * this method has been called previously without a call to close and forCompilation is set
+ * to false the previous compilation will be used. Setting forceCompilation to true will
+ * always re-compile the program and should be done if you pass differing SQL strings to this
+ * method.
+ *
+ * <P>Note: this method acquires the database lock.</P>
+ *
+ * @param sql the SQL string to compile
+ * @param forceCompilation forces the SQL to be recompiled in the event that there is an
+ * existing compiled SQL program already around
+ */
+ private void compile(String sql, boolean forceCompilation) {
+ // Only compile if we don't have a valid statement already or the caller has
+ // explicitly requested a recompile.
+ if (forceCompilation) {
+ mDatabase.lock();
+ try {
+ // Note that the native_compile() takes care of destroying any previously
+ // existing programs before it compiles.
+ native_compile(sql);
+ } finally {
+ mDatabase.unlock();
+ }
+ }
+ }
+
+ /* package */ void releaseSqlStatement() {
+ // Note that native_finalize() checks to make sure that nStatement is
+ // non-null before destroying it.
+ if (nStatement != 0) {
+ try {
+ mDatabase.lock();
+ native_finalize();
+ nStatement = 0;
+ } finally {
+ mDatabase.unlock();
+ }
+ }
+ }
+
+ /**
+ * Make sure that the native resource is cleaned up.
+ */
+ @Override
+ protected void finalize() {
+ releaseSqlStatement();
+ }
+
+ /**
+ * Compiles SQL into a SQLite program.
+ *
+ * <P>The database lock must be held when calling this method.
+ * @param sql The SQL to compile.
+ */
+ private final native void native_compile(String sql);
+ private final native void native_finalize();
+}
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 70b9b83..b178d4f 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -582,21 +582,23 @@ public class SQLiteCursor extends AbstractWindowedCursor {
@Override
protected void finalize() {
try {
+ // if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
close();
- String message = "Finalizing cursor " + this + " on " + mEditTable
- + " that has not been deactivated or closed";
+ Log.e(TAG, "Finalizing cursor that has not been deactivated or closed."
+ + " database = " + mDatabase.getPath() + ", table = " + mEditTable
+ + ", query = " + mQuery.mSql);
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
- Log.d(TAG, message + "\nThis cursor was created in:");
+ Log.d(TAG, "This cursor was created in:");
for (StackTraceElement ste : mStackTraceElements) {
Log.d(TAG, " " + ste);
}
}
SQLiteDebug.notifyActiveCursorFinalized();
- throw new IllegalStateException(message);
} else {
if (Config.LOGV) {
- Log.v(TAG, "Finalizing cursor " + this + " on " + mEditTable);
+ Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() +
+ ", table = " + mEditTable + ", query = " + mQuery.mSql);
}
}
} finally {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 9ebf5d9..f310586 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,6 +16,9 @@
package android.database.sqlite;
+import com.google.android.collect.Maps;
+
+import android.app.ActivityThread;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -29,10 +32,12 @@ import android.util.EventLog;
import android.util.Log;
import java.io.File;
+import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
+import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantLock;
@@ -200,6 +205,11 @@ public class SQLiteDatabase extends SQLiteClosable {
private long mLastLockMessageTime = 0L;
+ // always log queries which take 100ms+; shorter queries are sampled accordingly
+ private static final int QUERY_LOG_TIME_IN_NANOS = 100 * 1000000;
+ private static final int QUERY_LOG_SQL_LENGTH = 64;
+ private final Random mRandom = new Random();
+
/** Used by native code, do not rename */
/* package */ int mNativeHandle = 0;
@@ -217,11 +227,35 @@ public class SQLiteDatabase extends SQLiteClosable {
private WeakHashMap<SQLiteClosable, Object> mPrograms;
- private final RuntimeException mLeakedException;
+ /**
+ * for each instance of this class, a cache is maintained to store
+ * the compiled query statement ids returned by sqlite database.
+ * key = sql statement with "?" for bind args
+ * value = {@link SQLiteCompiledSql}
+ * If an application opens the database and keeps it open during its entire life, then
+ * there will not be an overhead of compilation of sql statements by sqlite.
+ *
+ * why is this cache NOT static? because sqlite attaches compiledsql statements to the
+ * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is
+ * invoked.
+ *
+ * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method
+ * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because
+ * most of the apps don't use "?" syntax in their sql, caching is not useful for them.
+ */
+ private Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
+ private int mMaxSqlCacheSize = 0; // no caching by default
+ private static final int MAX_SQL_CACHE_SIZE = 1000;
- // package visible, since callers will access directly to minimize overhead in the case
- // that logging is not enabled.
- /* package */ final boolean mLogStats;
+ /** maintain stats about number of cache hits and misses */
+ private int mNumCacheHits;
+ private int mNumCacheMisses;
+
+ /** the following 2 members maintain the time when a database is opened and closed */
+ private String mTimeOpened = null;
+ private String mTimeClosed = null;
+
+ private final RuntimeException mLeakedException;
// System property that enables logging of slow queries. Specify the threshold in ms.
private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold";
@@ -251,6 +285,9 @@ public class SQLiteDatabase extends SQLiteClosable {
@Override
protected void onAllReferencesReleased() {
if (isOpen()) {
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ mTimeClosed = getTime();
+ }
dbclose();
}
}
@@ -799,6 +836,13 @@ public class SQLiteDatabase extends SQLiteClosable {
program.onAllReferencesReleasedFromContainer();
}
}
+
+ // finalize all compiled sql statement objects in compiledQueries cache
+ synchronized (mCompiledQueries) {
+ for (SQLiteCompiledSql compiledStatement : mCompiledQueries.values()) {
+ compiledStatement.releaseSqlStatement();
+ }
+ }
}
/**
@@ -1603,8 +1647,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException If the SQL string is invalid for some reason
*/
public void execSQL(String sql) throws SQLException {
- boolean logStats = mLogStats;
- long timeStart = logStats ? SystemClock.elapsedRealtime() : 0;
+ long timeStart = Debug.threadCpuTimeNanos();
lock();
try {
native_execSQL(sql);
@@ -1614,9 +1657,7 @@ public class SQLiteDatabase extends SQLiteClosable {
} finally {
unlock();
}
- if (logStats) {
- logTimeStat(false /* not a read */, timeStart, SystemClock.elapsedRealtime());
- }
+ logTimeStat(sql, timeStart);
}
/**
@@ -1633,8 +1674,7 @@ public class SQLiteDatabase extends SQLiteClosable {
throw new IllegalArgumentException("Empty bindArgs");
}
- boolean logStats = mLogStats;
- long timeStart = logStats ? SystemClock.elapsedRealtime() : 0;
+ long timeStart = Debug.threadCpuTimeNanos();
lock();
SQLiteStatement statement = null;
try {
@@ -1655,9 +1695,7 @@ public class SQLiteDatabase extends SQLiteClosable {
}
unlock();
}
- if (logStats) {
- logTimeStat(false /* not a read */, timeStart, SystemClock.elapsedRealtime());
- }
+ logTimeStat(sql, timeStart);
}
@Override
@@ -1689,23 +1727,32 @@ public class SQLiteDatabase extends SQLiteClosable {
}
mFlags = flags;
mPath = path;
- mLogStats = "1".equals(android.os.SystemProperties.get("db.logstats"));
mSlowQueryThreshold = SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1);
mLeakedException = new IllegalStateException(path +
" SQLiteDatabase created and never closed");
mFactory = factory;
dbopen(mPath, mFlags);
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ mTimeOpened = getTime();
+ }
mPrograms = new WeakHashMap<SQLiteClosable,Object>();
try {
setLocale(Locale.getDefault());
} catch (RuntimeException e) {
Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e);
dbclose();
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ mTimeClosed = getTime();
+ }
throw e;
}
}
+ private String getTime() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis());
+ }
+
/**
* return whether the DB is opened as read only.
* @return true if DB is opened as read only
@@ -1734,8 +1781,141 @@ public class SQLiteDatabase extends SQLiteClosable {
return mPath;
}
- /* package */ void logTimeStat(boolean read, long begin, long end) {
- EventLog.writeEvent(EVENT_DB_OPERATION, mPath, read ? 0 : 1, end - begin);
+ /**
+ * set the max size of the compiled sql cache for this database after purging the cache.
+ * (size of the cache = number of compiled-sql-statements stored in the cache)
+ *
+ * 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)
+ */
+ public void setMaxSqlCacheSize(int cacheSize) {
+ synchronized(mCompiledQueries) {
+ resetCompiledSqlCache();
+ mMaxSqlCacheSize = (cacheSize > MAX_SQL_CACHE_SIZE) ? MAX_SQL_CACHE_SIZE
+ : (cacheSize < 0) ? 0 : cacheSize;
+ }
+ }
+
+ /**
+ * remove everything from the compiled sql cache
+ */
+ public void resetCompiledSqlCache() {
+ synchronized(mCompiledQueries) {
+ mCompiledQueries.clear();
+ }
+ }
+
+ /**
+ * adds the given sql and its compiled-statement-id-returned-by-sqlite to the
+ * cache of compiledQueries attached to 'this'.
+ *
+ * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql,
+ * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current
+ * mapping is NOT replaced with the new mapping).
+ *
+ * @return true if the given obj is added to cache. false otherwise.
+ */
+ /* package */ boolean 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 false;
+ }
+
+ SQLiteCompiledSql compiledSql = null;
+ synchronized(mCompiledQueries) {
+ // don't insert the new mapping if a mapping already exists
+ compiledSql = mCompiledQueries.get(sql);
+ if (compiledSql != null) {
+ return false;
+ }
+ // add this <sql, compiledStatement> to the cache
+ if (mCompiledQueries.size() == mMaxSqlCacheSize) {
+ /* reached max cachesize. before adding new entry, remove an entry from the
+ * cache. we don't want to wipe out the entire cache because of this:
+ * GCing {@link SQLiteCompiledSql} requires call to sqlite3_finalize
+ * JNI method. If entire cache is wiped out, it could be cause a big GC activity
+ * just because a (rogue) process is using the cache incorrectly.
+ */
+ Set<String> keySet = mCompiledQueries.keySet();
+ for (String s : keySet) {
+ mCompiledQueries.remove(s);
+ break;
+ }
+ }
+ compiledSql = new SQLiteCompiledSql(this, sql);
+ mCompiledQueries.put(sql, compiledSql);
+ }
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + mCompiledQueries.size() + "|" +
+ sql);
+ }
+ return true;
+ }
+
+ /**
+ * from the compiledQueries cache, returns the compiled-statement-id for the given sql.
+ * returns null, if not found in the cache.
+ */
+ /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) {
+ 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) {
+ mNumCacheHits++;
+ } else {
+ mNumCacheMisses++;
+ }
+
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|cache_stats|" +
+ getPath() + "|" + mCompiledQueries.size() +
+ "|" + mNumCacheHits + "|" + mNumCacheMisses +
+ "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql);
+ }
+ return compiledStatement;
+ }
+
+ /* package */ void logTimeStat(String sql, long beginNanos) {
+ // Sample fast queries in proportion to the time taken.
+ // Quantize the % first, so the logged sampling probability
+ // exactly equals the actual sampling rate for this query.
+
+ int samplePercent;
+ long nanos = Debug.threadCpuTimeNanos() - beginNanos;
+ if (nanos >= QUERY_LOG_TIME_IN_NANOS) {
+ samplePercent = 100;
+ } else {
+ samplePercent = (int) (100 * nanos / QUERY_LOG_TIME_IN_NANOS) + 1;
+ if (mRandom.nextInt(100) >= samplePercent) return;
+ }
+
+ if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH);
+
+ // ActivityThread.currentPackageName() only returns non-null if the
+ // current thread is an application main thread. This parameter tells
+ // us whether an event loop is blocked, and if so, which app it is.
+ //
+ // Sadly, there's no fast way to determine app name if this is *not* a
+ // main thread, or when we are invoked via Binder (e.g. ContentProvider).
+ // Hopefully the full path to the database will be informative enough.
+
+ String blockingPackage = ActivityThread.currentPackageName();
+ if (blockingPackage == null) blockingPackage = "";
+
+ int millis = (int) (nanos / 1000000);
+ EventLog.writeEvent(EVENT_DB_OPERATION, mPath, sql, millis, blockingPackage, samplePercent);
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 84d8879..d4d3059 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -32,6 +32,12 @@ public final class SQLiteDebug {
Log.isLoggable("SQLiteStatements", Log.VERBOSE);
/**
+ * Controls the printing of compiled-sql-statement cache stats.
+ */
+ public static final boolean DEBUG_SQL_CACHE =
+ Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);
+
+ /**
* Controls the stack trace reporting of active cursors being
* finalized.
*/
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 9e85452..edc15cb 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -27,6 +27,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
/** The database this program is compiled against. */
protected SQLiteDatabase mDatabase;
+ /** The SQL used to create this query */
+ /* package */ final String mSql;
+
/**
* Native linkage, do not modify. This comes from the database and should not be modified
* in here or in the native code.
@@ -34,87 +37,88 @@ public abstract class SQLiteProgram extends SQLiteClosable {
protected int nHandle = 0;
/**
- * Native linkage, do not modify. When non-0 this holds a reference to a valid
- * sqlite3_statement object. It is only updated by the native code, but may be
- * checked in this class when the database lock is held to determine if there
- * is a valid native-side program or not.
+ * the compiledSql object for the given sql statement.
*/
- protected int nStatement = 0;
+ private SQLiteCompiledSql compiledSql;
+ private boolean myCompiledSqlIsInCache;
/**
- * Used to find out where a cursor was allocated in case it never got
- * released.
+ * compiledSql statement id is populated with the corresponding object from the above
+ * member compiledSql.
+ * this member is used by the native_bind_* methods
*/
- private StackTraceElement[] mStackTraceElements;
-
+ protected int nStatement = 0;
+
/* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- mStackTraceElements = new Exception().getStackTrace();
+ Log.d(TAG, "processing sql: " + sql);
}
-
+
mDatabase = db;
+ mSql = sql;
db.acquireReference();
db.addSQLiteClosable(this);
this.nHandle = db.mNativeHandle;
- compile(sql, false);
- }
-
+
+ compiledSql = db.getCompiledStatementForSql(sql);
+ if (compiledSql == null) {
+ // create a new compiled-sql obj
+ compiledSql = new SQLiteCompiledSql(db, sql);
+
+ // add it to the cache of compiled-sqls
+ myCompiledSqlIsInCache = db.addToCompiledQueries(sql, compiledSql);
+ } else {
+ myCompiledSqlIsInCache = true;
+ }
+ nStatement = compiledSql.nStatement;
+ }
+
@Override
protected void onAllReferencesReleased() {
- // Note that native_finalize() checks to make sure that nStatement is
- // non-null before destroying it.
- native_finalize();
+ // release the compiled sql statement used by me if it is NOT in cache
+ if (!myCompiledSqlIsInCache && compiledSql != null) {
+ compiledSql.releaseSqlStatement();
+ compiledSql = null; // so that GC doesn't call finalize() on it
+ }
mDatabase.releaseReference();
mDatabase.removeSQLiteClosable(this);
}
-
+
@Override
- protected void onAllReferencesReleasedFromContainer(){
- // Note that native_finalize() checks to make sure that nStatement is
- // non-null before destroying it.
- native_finalize();
- mDatabase.releaseReference();
+ protected void onAllReferencesReleasedFromContainer() {
+ // release the compiled sql statement used by me if it is NOT in cache
+ if (!myCompiledSqlIsInCache && compiledSql != null) {
+ compiledSql.releaseSqlStatement();
+ compiledSql = null; // so that GC doesn't call finalize() on it
+ }
+ mDatabase.releaseReference();
}
/**
* Returns a unique identifier for this program.
- *
+ *
* @return a unique identifier for this program
*/
public final int getUniqueId() {
- return nStatement;
+ return compiledSql.nStatement;
+ }
+
+ /* package */ String getSqlString() {
+ return mSql;
}
/**
- * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
- * this method has been called previously without a call to close and forCompilation is set
- * to false the previous compilation will be used. Setting forceCompilation to true will
- * always re-compile the program and should be done if you pass differing SQL strings to this
- * method.
- *
- * <P>Note: this method acquires the database lock.</P>
+ * @deprecated use this.compiledStatement.compile instead
*
* @param sql the SQL string to compile
* @param forceCompilation forces the SQL to be recompiled in the event that there is an
* existing compiled SQL program already around
*/
+ @Deprecated
protected void compile(String sql, boolean forceCompilation) {
- // Only compile if we don't have a valid statement already or the caller has
- // explicitly requested a recompile.
- if (nStatement == 0 || forceCompilation) {
- mDatabase.lock();
- try {
- // Note that the native_compile() takes care of destroying any previously
- // existing programs before it compiles.
- acquireReference();
- native_compile(sql);
- } finally {
- releaseReference();
- mDatabase.unlock();
- }
- }
- }
-
+ // TODO is there a need for this?
+ }
+
/**
* Bind a NULL value to this statement. The value remains bound until
* {@link #clearBindings} is called.
@@ -221,37 +225,18 @@ public abstract class SQLiteProgram extends SQLiteClosable {
releaseReference();
} finally {
mDatabase.unlock();
- }
- }
-
- /**
- * Make sure that the native resource is cleaned up.
- */
- @Override
- protected void finalize() {
- if (nStatement != 0) {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- String message = "Finalizing " + this +
- " that has not been closed";
-
- Log.d(TAG, message + "\nThis cursor was created in:");
- for (StackTraceElement ste : mStackTraceElements) {
- Log.d(TAG, " " + ste);
- }
- }
- // when in finalize() it is already removed from weakhashmap
- // so it is safe to not removed itself from db
- onAllReferencesReleasedFromContainer();
}
}
/**
* Compiles SQL into a SQLite program.
- *
+ *
* <P>The database lock must be held when calling this method.
* @param sql The SQL to compile.
*/
+ @Deprecated
protected final native void native_compile(String sql);
+ @Deprecated
protected final native void native_finalize();
protected final native void native_bind_null(int index);
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index cdd9f86..c34661d 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -17,6 +17,7 @@
package android.database.sqlite;
import android.database.CursorWindow;
+import android.os.Debug;
import android.os.SystemClock;
import android.util.Log;
@@ -30,9 +31,6 @@ public class SQLiteQuery extends SQLiteProgram {
/** The index of the unbound OFFSET parameter */
private int mOffsetIndex;
- /** The SQL used to create this query */
- private String mQuery;
-
/** Args to bind on requery */
private String[] mBindArgs;
@@ -49,7 +47,6 @@ public class SQLiteQuery extends SQLiteProgram {
super(db, query);
mOffsetIndex = offsetIndex;
- mQuery = query;
mBindArgs = bindArgs;
}
@@ -61,10 +58,9 @@ public class SQLiteQuery extends SQLiteProgram {
*/
/* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
+ long timeStart = Debug.threadCpuTimeNanos();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
try {
acquireReference();
try {
@@ -77,12 +73,9 @@ public class SQLiteQuery extends SQLiteProgram {
// Logging
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.d(TAG, "fillWindow(): " + mQuery);
- }
- if (logStats) {
- mDatabase.logTimeStat(true /* read */, startTime,
- SystemClock.elapsedRealtime());
+ Log.d(TAG, "fillWindow(): " + mSql);
}
+ mDatabase.logTimeStat(mSql, timeStart);
return numRows;
} catch (IllegalStateException e){
// simply ignore it
@@ -133,7 +126,7 @@ public class SQLiteQuery extends SQLiteProgram {
@Override
public String toString() {
- return "SQLiteQuery: " + mQuery;
+ return "SQLiteQuery: " + mSql;
}
@Override
@@ -153,7 +146,7 @@ public class SQLiteQuery extends SQLiteProgram {
super.bindString(i + 1, mBindArgs[i]);
}
} catch (SQLiteMisuseException e) {
- StringBuilder errMsg = new StringBuilder("mQuery " + mQuery);
+ StringBuilder errMsg = new StringBuilder("mSql " + mSql);
for (int i = 0; i < len; i++) {
errMsg.append(" ");
errMsg.append(mBindArgs[i]);
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index 5889ad9..cc714ee 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,6 +16,7 @@
package android.database.sqlite;
+import android.os.Debug;
import android.os.SystemClock;
import android.util.Log;
@@ -29,8 +30,6 @@ public class SQLiteStatement extends SQLiteProgram
{
private static final String TAG = "SQLiteStatement";
- private final String mSql;
-
/**
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
@@ -39,11 +38,6 @@ public class SQLiteStatement extends SQLiteProgram
*/
/* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
super(db, sql);
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- mSql = sql;
- } else {
- mSql = null;
- }
}
/**
@@ -54,9 +48,8 @@ public class SQLiteStatement extends SQLiteProgram
* some reason
*/
public void execute() {
+ long timeStart = Debug.threadCpuTimeNanos();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
acquireReference();
try {
@@ -64,10 +57,8 @@ public class SQLiteStatement extends SQLiteProgram
Log.v(TAG, "execute() for [" + mSql + "]");
}
native_execute();
- if (logStats) {
- mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
- }
- } finally {
+ mDatabase.logTimeStat(mSql, timeStart);
+ } finally {
releaseReference();
mDatabase.unlock();
}
@@ -84,9 +75,8 @@ public class SQLiteStatement extends SQLiteProgram
* some reason
*/
public long executeInsert() {
+ long timeStart = Debug.threadCpuTimeNanos();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
acquireReference();
try {
@@ -94,9 +84,7 @@ public class SQLiteStatement extends SQLiteProgram
Log.v(TAG, "executeInsert() for [" + mSql + "]");
}
native_execute();
- if (logStats) {
- mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
- }
+ mDatabase.logTimeStat(mSql, timeStart);
return mDatabase.lastInsertRow();
} finally {
releaseReference();
@@ -113,9 +101,8 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public long simpleQueryForLong() {
+ long timeStart = Debug.threadCpuTimeNanos();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
acquireReference();
try {
@@ -123,9 +110,7 @@ public class SQLiteStatement extends SQLiteProgram
Log.v(TAG, "simpleQueryForLong() for [" + mSql + "]");
}
long retValue = native_1x1_long();
- if (logStats) {
- mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
- }
+ mDatabase.logTimeStat(mSql, timeStart);
return retValue;
} finally {
releaseReference();
@@ -142,9 +127,8 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public String simpleQueryForString() {
+ long timeStart = Debug.threadCpuTimeNanos();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
acquireReference();
try {
@@ -152,9 +136,7 @@ public class SQLiteStatement extends SQLiteProgram
Log.v(TAG, "simpleQueryForString() for [" + mSql + "]");
}
String retValue = native_1x1_string();
- if (logStats) {
- mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
- }
+ mDatabase.logTimeStat(mSql, timeStart);
return retValue;
} finally {
releaseReference();
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index c5d591f..0603ca5 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -152,8 +152,8 @@ public class DdmHandleHello extends ChunkHandler {
"hprof-heap-dump", "method-trace-profiling"
};
- if (Config.LOGD)
- Log.d("ddm-heap", "Got feature list request");
+ if (Config.LOGV)
+ Log.v("ddm-heap", "Got feature list request");
int size = 4 + 4 * features.length;
for (int i = features.length-1; i >= 0; i--)
diff --git a/core/java/android/gesture/GestureStore.java b/core/java/android/gesture/GestureStore.java
index 5f1a445..11a94d1 100644
--- a/core/java/android/gesture/GestureStore.java
+++ b/core/java/android/gesture/GestureStore.java
@@ -65,7 +65,12 @@ public class GestureStore {
// ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures
public static final int ORIENTATION_INVARIANT = 1;
+ // at most 2 directions can be recognized
public static final int ORIENTATION_SENSITIVE = 2;
+ // at most 4 directions can be recognized
+ static final int ORIENTATION_SENSITIVE_4 = 4;
+ // at most 8 directions can be recognized
+ static final int ORIENTATION_SENSITIVE_8 = 8;
private static final short FILE_FORMAT_VERSION = 1;
@@ -131,7 +136,7 @@ public class GestureStore {
public ArrayList<Prediction> recognize(Gesture gesture) {
Instance instance = Instance.createInstance(mSequenceType,
mOrientationStyle, gesture, null);
- return mClassifier.classify(mSequenceType, instance.vector);
+ return mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector);
}
/**
diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtilities.java
index 40d7029..f1dcd89 100755
--- a/core/java/android/gesture/GestureUtilities.java
+++ b/core/java/android/gesture/GestureUtilities.java
@@ -366,6 +366,38 @@ final class GestureUtilities {
}
return Math.acos(sum);
}
+
+ /**
+ * Calculate the "minimum" cosine distance between two instances
+ *
+ * @param vector1
+ * @param vector2
+ * @param numOrientations the maximum number of orientation allowed
+ * @return the distance between the two instances (between 0 and Math.PI)
+ */
+ static double minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) {
+ final int len = vector1.length;
+ double a = 0;
+ double b = 0;
+ for (int i = 0; i < len; i += 2) {
+ a += vector1[i] * vector2[i] + vector1[i + 1] * vector2[i + 1];
+ b += vector1[i] * vector2[i + 1] - vector1[i + 1] * vector2[i];
+ }
+ if (a != 0) {
+ final double tan = b/a;
+ final double angle = Math.atan(tan);
+ if (numOrientations > 2 && Math.abs(angle) >= Math.PI / numOrientations) {
+ return Math.acos(a);
+ } else {
+ final double cosine = Math.cos(angle);
+ final double sine = cosine * tan;
+ return Math.acos(a * cosine + b * sine);
+ }
+ } else {
+ return Math.PI / 2;
+ }
+ }
+
static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> pts) {
GestureStroke stroke = new GestureStroke(pts);
diff --git a/core/java/android/gesture/Instance.java b/core/java/android/gesture/Instance.java
index ef208ac..68a2985 100755
--- a/core/java/android/gesture/Instance.java
+++ b/core/java/android/gesture/Instance.java
@@ -94,7 +94,7 @@ class Instance {
float orientation = (float)Math.atan2(pts[1] - center[1], pts[0] - center[0]);
float adjustment = -orientation;
- if (orientationType == GestureStore.ORIENTATION_SENSITIVE) {
+ if (orientationType != GestureStore.ORIENTATION_INVARIANT) {
int count = ORIENTATIONS.length;
for (int i = 0; i < count; i++) {
float delta = ORIENTATIONS[i] - orientation;
diff --git a/core/java/android/gesture/InstanceLearner.java b/core/java/android/gesture/InstanceLearner.java
index b93b76f..9987e69 100644
--- a/core/java/android/gesture/InstanceLearner.java
+++ b/core/java/android/gesture/InstanceLearner.java
@@ -41,7 +41,7 @@ class InstanceLearner extends Learner {
};
@Override
- ArrayList<Prediction> classify(int sequenceType, float[] vector) {
+ ArrayList<Prediction> classify(int sequenceType, int orientationType, float[] vector) {
ArrayList<Prediction> predictions = new ArrayList<Prediction>();
ArrayList<Instance> instances = getInstances();
int count = instances.size();
@@ -53,7 +53,7 @@ class InstanceLearner extends Learner {
}
double distance;
if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
- distance = GestureUtilities.cosineDistance(sample.vector, vector);
+ distance = GestureUtilities.minimumCosineDistance(sample.vector, vector, orientationType);
} else {
distance = GestureUtilities.squaredEuclideanDistance(sample.vector, vector);
}
diff --git a/core/java/android/gesture/Learner.java b/core/java/android/gesture/Learner.java
index feacde5..60997e0 100755
--- a/core/java/android/gesture/Learner.java
+++ b/core/java/android/gesture/Learner.java
@@ -79,5 +79,5 @@ abstract class Learner {
instances.removeAll(toDelete);
}
- abstract ArrayList<Prediction> classify(int gestureType, float[] vector);
+ abstract ArrayList<Prediction> classify(int sequenceType, int orientationType, float[] vector);
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 271f973..0c6bb1a 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -364,7 +364,7 @@ public class SensorManager
final float[] values = new float[3];
final int[] status = new int[1];
final long timestamp[] = new long[1];
- Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
+ Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
if (!open()) {
return;
@@ -545,8 +545,8 @@ public class SensorManager
i = sensors_module_get_next_sensor(sensor, i);
if (i>=0) {
- Log.d(TAG, "found sensor: " + sensor.getName() +
- ", handle=" + sensor.getHandle());
+ //Log.d(TAG, "found sensor: " + sensor.getName() +
+ // ", handle=" + sensor.getHandle());
sensor.setLegacyType(getLegacySensorType(sensor.getType()));
fullList.add(sensor);
sHandleToSensor.append(sensor.getHandle(), sensor);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 5499bba..b315932 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,7 +16,7 @@
package android.inputmethodservice;
-import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.Dialog;
@@ -556,7 +556,7 @@ public class InputMethodService extends AbstractInputMethodService {
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
initViews();
- mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
+ mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
}
/**
@@ -803,8 +803,8 @@ public class InputMethodService extends AbstractInputMethodService {
* candidates only mode changes.
*
* <p>The default implementation makes the layout for the window
- * FILL_PARENT x FILL_PARENT when in fullscreen mode, and
- * FILL_PARENT x WRAP_CONTENT when in non-fullscreen mode.
+ * MATCH_PARENT x MATCH_PARENT when in fullscreen mode, and
+ * MATCH_PARENT x WRAP_CONTENT when in non-fullscreen mode.
*
* @param win The input method's window.
* @param isFullscreen If true, the window is running in fullscreen mode
@@ -816,9 +816,9 @@ public class InputMethodService extends AbstractInputMethodService {
public void onConfigureWindow(Window win, boolean isFullscreen,
boolean isCandidatesOnly) {
if (isFullscreen) {
- mWindow.getWindow().setLayout(FILL_PARENT, FILL_PARENT);
+ mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT);
} else {
- mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
+ mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
}
}
@@ -1049,8 +1049,8 @@ public class InputMethodService extends AbstractInputMethodService {
public void setExtractView(View view) {
mExtractFrame.removeAllViews();
mExtractFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT));
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
mExtractView = view;
if (view != null) {
mExtractEditText = (ExtractEditText)view.findViewById(
@@ -1079,7 +1079,7 @@ public class InputMethodService extends AbstractInputMethodService {
public void setCandidatesView(View view) {
mCandidatesFrame.removeAllViews();
mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
}
@@ -1092,7 +1092,7 @@ public class InputMethodService extends AbstractInputMethodService {
public void setInputView(View view) {
mInputFrame.removeAllViews();
mInputFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
mInputView = view;
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 0f7ef22..b0c3909 100755
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -1118,6 +1118,11 @@ public class KeyboardView extends View implements View.OnClickListener {
if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
mSwipeTracker.addMovement(me);
+ // Ignore all motion events until a DOWN.
+ if (mAbortKey && action != MotionEvent.ACTION_DOWN) {
+ return true;
+ }
+
if (mGestureDetector.onTouchEvent(me)) {
showPreview(NOT_A_KEY);
mHandler.removeMessages(MSG_REPEAT);
@@ -1127,7 +1132,7 @@ public class KeyboardView extends View implements View.OnClickListener {
// Needs to be called after the gesture detector gets a turn, as it may have
// displayed the mini keyboard
- if (mMiniKeyboardOnScreen) {
+ if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
return true;
}
@@ -1150,9 +1155,14 @@ public class KeyboardView extends View implements View.OnClickListener {
mKeys[keyIndex].codes[0] : 0);
if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
mRepeatKeyIndex = mCurrentKey;
- repeatKey();
Message msg = mHandler.obtainMessage(MSG_REPEAT);
mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
+ repeatKey();
+ // Delivering the key could have caused an abort
+ if (mAbortKey) {
+ mRepeatKeyIndex = NOT_A_KEY;
+ break;
+ }
}
if (mCurrentKey != NOT_A_KEY) {
Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
@@ -1222,6 +1232,7 @@ public class KeyboardView extends View implements View.OnClickListener {
break;
case MotionEvent.ACTION_CANCEL:
removeMessages();
+ dismissPopupKeyboard();
mAbortKey = true;
showPreview(NOT_A_KEY);
invalidateKey(mCurrentKey);
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 538e51a..b254961 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -60,12 +60,11 @@ public class MobileDataStateTracker extends NetworkStateTracker {
* @param apnType the Phone apnType
* @param tag the name of this network
*/
- public MobileDataStateTracker(Context context, Handler target,
- int netType, String apnType, String tag) {
+ public MobileDataStateTracker(Context context, Handler target, int netType, String tag) {
super(context, target, netType,
TelephonyManager.getDefault().getNetworkType(), tag,
TelephonyManager.getDefault().getNetworkTypeName());
- mApnType = apnType;
+ mApnType = networkTypeToApnType(netType);
mPhoneService = null;
if(netType == ConnectivityManager.TYPE_MOBILE) {
mEnabled = true;
@@ -501,4 +500,22 @@ public class MobileDataStateTracker extends NetworkStateTracker {
+ " APN type \"" + apnType + "\"");
return Phone.APN_REQUEST_FAILED;
}
+
+ public static String networkTypeToApnType(int netType) {
+ switch(netType) {
+ case ConnectivityManager.TYPE_MOBILE:
+ return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these
+ case ConnectivityManager.TYPE_MOBILE_MMS:
+ return Phone.APN_TYPE_MMS;
+ case ConnectivityManager.TYPE_MOBILE_SUPL:
+ return Phone.APN_TYPE_SUPL;
+ case ConnectivityManager.TYPE_MOBILE_DUN:
+ return Phone.APN_TYPE_DUN;
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ return Phone.APN_TYPE_HIPRI;
+ default:
+ Log.e(TAG, "Error mapping networkType " + netType + " to apnType.");
+ return null;
+ }
+ }
}
diff --git a/core/java/android/net/NetworkConnectivityListener.java b/core/java/android/net/NetworkConnectivityListener.java
deleted file mode 100644
index 858fc77..0000000
--- a/core/java/android/net/NetworkConnectivityListener.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * 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.net;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-/**
- * A wrapper for a broadcast receiver that provides network connectivity
- * state information, independent of network type (mobile, Wi-Fi, etc.).
- * {@hide}
- */
-public class NetworkConnectivityListener {
- private static final String TAG = "NetworkConnectivityListener";
- private static final boolean DBG = false;
-
- private Context mContext;
- private HashMap<Handler, Integer> mHandlers = new HashMap<Handler, Integer>();
- private State mState;
- private boolean mListening;
- private String mReason;
- private boolean mIsFailover;
-
- /** Network connectivity information */
- private NetworkInfo mNetworkInfo;
-
- /**
- * In case of a Disconnect, the connectivity manager may have
- * already established, or may be attempting to establish, connectivity
- * with another network. If so, {@code mOtherNetworkInfo} will be non-null.
- */
- private NetworkInfo mOtherNetworkInfo;
-
- private ConnectivityBroadcastReceiver mReceiver;
-
- private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
-
- if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
- mListening == false) {
- Log.w(TAG, "onReceived() called with " + mState.toString() + " and " + intent);
- return;
- }
-
- boolean noConnectivity =
- intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
-
- if (noConnectivity) {
- mState = State.NOT_CONNECTED;
- } else {
- mState = State.CONNECTED;
- }
-
- mNetworkInfo = (NetworkInfo)
- intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
- mOtherNetworkInfo = (NetworkInfo)
- intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
-
- mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
- mIsFailover =
- intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
-
- if (DBG) {
- Log.d(TAG, "onReceive(): mNetworkInfo=" + mNetworkInfo + " mOtherNetworkInfo = "
- + (mOtherNetworkInfo == null ? "[none]" : mOtherNetworkInfo +
- " noConn=" + noConnectivity) + " mState=" + mState.toString());
- }
-
- // Notifiy any handlers.
- Iterator<Handler> it = mHandlers.keySet().iterator();
- while (it.hasNext()) {
- Handler target = it.next();
- Message message = Message.obtain(target, mHandlers.get(target));
- target.sendMessage(message);
- }
- }
- };
-
- public enum State {
- UNKNOWN,
-
- /** This state is returned if there is connectivity to any network **/
- CONNECTED,
- /**
- * This state is returned if there is no connectivity to any network. This is set
- * to true under two circumstances:
- * <ul>
- * <li>When connectivity is lost to one network, and there is no other available
- * network to attempt to switch to.</li>
- * <li>When connectivity is lost to one network, and the attempt to switch to
- * another network fails.</li>
- */
- NOT_CONNECTED
- }
-
- /**
- * Create a new NetworkConnectivityListener.
- */
- public NetworkConnectivityListener() {
- mState = State.UNKNOWN;
- mReceiver = new ConnectivityBroadcastReceiver();
- }
-
- /**
- * This method starts listening for network connectivity state changes.
- * @param context
- */
- public synchronized void startListening(Context context) {
- if (!mListening) {
- mContext = context;
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- context.registerReceiver(mReceiver, filter);
- mListening = true;
- }
- }
-
- /**
- * This method stops this class from listening for network changes.
- */
- public synchronized void stopListening() {
- if (mListening) {
- mContext.unregisterReceiver(mReceiver);
- mContext = null;
- mNetworkInfo = null;
- mOtherNetworkInfo = null;
- mIsFailover = false;
- mReason = null;
- mListening = false;
- }
- }
-
- /**
- * This methods registers a Handler to be called back onto with the specified what code when
- * the network connectivity state changes.
- *
- * @param target The target handler.
- * @param what The what code to be used when posting a message to the handler.
- */
- public void registerHandler(Handler target, int what) {
- mHandlers.put(target, what);
- }
-
- /**
- * This methods unregisters the specified Handler.
- * @param target
- */
- public void unregisterHandler(Handler target) {
- mHandlers.remove(target);
- }
-
- public State getState() {
- return mState;
- }
-
- /**
- * Return the NetworkInfo associated with the most recent connectivity event.
- * @return {@code NetworkInfo} for the network that had the most recent connectivity event.
- */
- public NetworkInfo getNetworkInfo() {
- return mNetworkInfo;
- }
-
- /**
- * If the most recent connectivity event was a DISCONNECT, return
- * any information supplied in the broadcast about an alternate
- * network that might be available. If this returns a non-null
- * value, then another broadcast should follow shortly indicating
- * whether connection to the other network succeeded.
- *
- * @return NetworkInfo
- */
- public NetworkInfo getOtherNetworkInfo() {
- return mOtherNetworkInfo;
- }
-
- /**
- * Returns true if the most recent event was for an attempt to switch over to
- * a new network following loss of connectivity on another network.
- * @return {@code true} if this was a failover attempt, {@code false} otherwise.
- */
- public boolean isFailover() {
- return mIsFailover;
- }
-
- /**
- * An optional reason for the connectivity state change may have been supplied.
- * This returns it.
- * @return the reason for the state change, if available, or {@code null}
- * otherwise.
- */
- public String getReason() {
- return mReason;
- }
-}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index a97b9e5..e40f1b8 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -16,11 +16,12 @@
package android.net;
-import android.net.http.DomainNameChecker;
import android.os.SystemProperties;
import android.util.Config;
import android.util.Log;
+import com.android.common.DomainNameValidator;
+
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
@@ -200,7 +201,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
X509Certificate lastChainCert = (X509Certificate) certs[0];
- if (!DomainNameChecker.match(lastChainCert, destHost)) {
+ if (!DomainNameValidator.match(lastChainCert, destHost)) {
if (Config.LOGD) {
Log.d(LOG_TAG,"validateSocket(): domain name check failed");
}
diff --git a/core/java/android/os/NetStat.java b/core/java/android/net/TrafficStats.java
index e294cdf..62e9f1f 100644
--- a/core/java/android/os/NetStat.java
+++ b/core/java/android/net/TrafficStats.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.os;
+package android.net;
import android.util.Log;
@@ -22,11 +22,22 @@ import java.io.File;
import java.io.RandomAccessFile;
import java.io.IOException;
-/** @hide */
-public class NetStat {
+/**
+ * Class that provides network traffic statistics. These statistics include bytes transmitted and
+ * received and network packets transmitted and received, over all interfaces, over the mobile
+ * interface, and on a per-UID basis.
+ * <p>
+ * These statistics may not be available on all platforms. If the statistics are not supported
+ * by this device, {@link #UNSUPPORTED} will be returned.
+ */
+public class TrafficStats {
+ /**
+ * The return value to indicate that the device does not support the statistic.
+ */
+ public final static int UNSUPPORTED = -1;
// Logging tag.
- private final static String TAG = "netstat";
+ private final static String TAG = "trafficstats";
// We pre-create all the File objects so we don't spend a lot of
// CPU at runtime converting from Java Strings to byte[] for the
@@ -38,36 +49,40 @@ public class NetStat {
private final static File SYS_CLASS_NET_DIR = new File("/sys/class/net");
/**
- * Get total number of tx packets sent through rmnet0 or ppp0
+ * Get the total number of packets transmitted through the mobile interface.
*
- * @return number of Tx packets through rmnet0 or ppp0
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getMobileTxPkts() {
return getMobileStat(MOBILE_TX_PACKETS);
}
/**
- * Get total number of rx packets received through rmnet0 or ppp0
+ * Get the total number of packets received through the mobile interface.
*
- * @return number of Rx packets through rmnet0 or ppp0
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getMobileRxPkts() {
return getMobileStat(MOBILE_RX_PACKETS);
}
/**
- * Get total number of tx bytes received through rmnet0 or ppp0
+ * Get the total number of bytes transmitted through the mobile interface.
*
- * @return number of Tx bytes through rmnet0 or ppp0
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getMobileTxBytes() {
return getMobileStat(MOBILE_TX_BYTES);
}
/**
- * Get total number of rx bytes received through rmnet0 or ppp0
+ * Get the total number of bytes received through the mobile interface.
*
- * @return number of Rx bytes through rmnet0 or ppp0
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getMobileRxBytes() {
return getMobileStat(MOBILE_RX_BYTES);
@@ -76,7 +91,8 @@ public class NetStat {
/**
* Get the total number of packets sent through all network interfaces.
*
- * @return the number of packets sent through all network interfaces
+ * @return the number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getTotalTxPkts() {
return getTotalStat("tx_packets");
@@ -85,7 +101,8 @@ public class NetStat {
/**
* Get the total number of packets received through all network interfaces.
*
- * @return the number of packets received through all network interfaces
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getTotalRxPkts() {
return getTotalStat("rx_packets");
@@ -94,7 +111,8 @@ public class NetStat {
/**
* Get the total number of bytes sent through all network interfaces.
*
- * @return the number of bytes sent through all network interfaces
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getTotalTxBytes() {
return getTotalStat("tx_bytes");
@@ -103,35 +121,35 @@ public class NetStat {
/**
* Get the total number of bytes received through all network interfaces.
*
- * @return the number of bytes received through all network interfaces
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getTotalRxBytes() {
return getTotalStat("rx_bytes");
}
/**
- * Gets network bytes sent for this UID.
+ * Get the number of bytes sent through the network for this UID.
* The statistics are across all interfaces.
- * The statistics come from /proc/uid_stat.
*
* {@see android.os.Process#myUid()}.
*
- * @param uid
- * @return byte count
+ * @param uid The UID of the process to examine.
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
*/
public static long getUidTxBytes(int uid) {
return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_snd");
}
/**
- * Gets network bytes received for this UID.
+ * Get the number of bytes received through the network for this UID.
* The statistics are across all interfaces.
- * The statistics come from /proc/uid_stat.
*
* {@see android.os.Process#myUid()}.
*
- * @param uid
- * @return byte count
+ * @param uid The UID of the process to examine.
+ * @return number of bytes
*/
public static long getUidRxBytes(int uid) {
return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_rcv");
@@ -159,7 +177,7 @@ public class NetStat {
File[] nets = SYS_CLASS_NET_DIR.listFiles();
if (nets == null) {
- return 0;
+ return UNSUPPORTED;
}
long total = 0;
StringBuffer strbuf = new StringBuffer();
@@ -187,14 +205,14 @@ public class NetStat {
e);
}
}
- return 0L;
+ return UNSUPPORTED;
}
// File will have format <number><newline>
private static long getNumberFromFilePath(String filename) {
RandomAccessFile raf = getFile(filename);
if (raf == null) {
- return 0L;
+ return UNSUPPORTED;
}
return getNumberFromFile(raf, filename);
}
@@ -209,7 +227,7 @@ public class NetStat {
raf.close();
} catch (IOException e) {
Log.w(TAG, "Exception getting TCP bytes from " + filename, e);
- return 0L;
+ return UNSUPPORTED;
} finally {
if (raf != null) {
try {
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 9a1b65d..f2ea539 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -1567,51 +1567,40 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
if (isOpaque()) {
throw new UnsupportedOperationException(NOT_HIERARCHICAL);
}
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
- String query = getEncodedQuery();
-
+ final String query = getEncodedQuery();
if (query == null) {
return null;
}
- String encodedKey;
- try {
- encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
- } catch (UnsupportedEncodingException e) {
- throw new AssertionError(e);
- }
-
- String prefix = encodedKey + "=";
+ final String encodedKey = encode(key, null);
+ final int encodedKeyLength = encodedKey.length();
- if (query.length() < prefix.length()) {
- return null;
- }
+ int encodedKeySearchIndex = 0;
+ final int encodedKeySearchEnd = query.length() - (encodedKeyLength + 1);
- int start;
- if (query.startsWith(prefix)) {
- // It's the first parameter.
- start = prefix.length();
- } else {
- // It must be later in the query string.
- prefix = "&" + prefix;
- start = query.indexOf(prefix);
-
- if (start == -1) {
- // Not found.
- return null;
+ while (encodedKeySearchIndex <= encodedKeySearchEnd) {
+ int keyIndex = query.indexOf(encodedKey, encodedKeySearchIndex);
+ if (keyIndex == -1) {
+ break;
+ }
+ final int equalsIndex = keyIndex + encodedKeyLength;
+ if (query.charAt(equalsIndex) != '=') {
+ encodedKeySearchIndex = equalsIndex + 1;
+ continue;
+ }
+ if (keyIndex == 0 || query.charAt(keyIndex - 1) == '&') {
+ int end = query.indexOf('&', equalsIndex);
+ if (end == -1) {
+ end = query.length();
+ }
+ return decode(query.substring(equalsIndex + 1, end));
}
-
- start += prefix.length();
- }
-
- // Find end of value.
- int end = query.indexOf('&', start);
- if (end == -1) {
- end = query.length();
}
-
- String value = query.substring(start, end);
- return decode(value);
+ return null;
}
/** Identifies a null parcelled Uri. */
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
deleted file mode 100644
index c2013d5..0000000
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ /dev/null
@@ -1,508 +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.net.http;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpEntityEnclosingRequest;
-import org.apache.http.HttpException;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.HttpResponse;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.protocol.ClientContext;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.params.HttpClientParams;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.ssl.SSLSocketFactory;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.client.RequestWrapper;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-import org.apache.http.protocol.BasicHttpProcessor;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.BasicHttpContext;
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-import java.net.URI;
-import java.security.KeyManagementException;
-
-import android.util.Log;
-import android.content.ContentResolver;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.os.SystemProperties;
-
-/**
- * Subclass of the Apache {@link DefaultHttpClient} that is configured with
- * reasonable default settings and registered schemes for Android, and
- * also lets the user add {@link HttpRequestInterceptor} classes.
- * Don't create this directly, use the {@link #newInstance} factory method.
- *
- * <p>This client processes cookies but does not retain them by default.
- * To retain cookies, simply add a cookie store to the HttpContext:</p>
- *
- * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
- *
- * {@hide}
- */
-public final class AndroidHttpClient implements HttpClient {
-
- // Gzip of data shorter than this probably won't be worthwhile
- public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
-
- private static final String TAG = "AndroidHttpClient";
-
-
- /** Set if HTTP requests are blocked from being executed on this thread */
- private static final ThreadLocal<Boolean> sThreadBlocked =
- new ThreadLocal<Boolean>();
-
- /** Interceptor throws an exception if the executing thread is blocked */
- private static final HttpRequestInterceptor sThreadCheckInterceptor =
- new HttpRequestInterceptor() {
- public void process(HttpRequest request, HttpContext context) {
- if (sThreadBlocked.get() != null && sThreadBlocked.get()) {
- throw new RuntimeException("This thread forbids HTTP requests");
- }
- }
- };
-
- /**
- * Create a new HttpClient with reasonable defaults (which you can update).
- *
- * @param userAgent to report in your HTTP requests.
- * @param sessionCache persistent session cache
- * @return AndroidHttpClient for you to use for all your requests.
- */
- public static AndroidHttpClient newInstance(String userAgent,
- SSLClientSessionCache sessionCache) {
- HttpParams params = new BasicHttpParams();
-
- // Turn off stale checking. Our connections break all the time anyway,
- // and it's not worth it to pay the penalty of checking every time.
- HttpConnectionParams.setStaleCheckingEnabled(params, false);
-
- // Default connection and socket timeout of 20 seconds. Tweak to taste.
- HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
- HttpConnectionParams.setSoTimeout(params, 20 * 1000);
- HttpConnectionParams.setSocketBufferSize(params, 8192);
-
- // Don't handle redirects -- return them to the caller. Our code
- // often wants to re-POST after a redirect, which we must do ourselves.
- HttpClientParams.setRedirecting(params, false);
-
- // Set the specified user agent and register standard protocols.
- HttpProtocolParams.setUserAgent(params, userAgent);
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(new Scheme("http",
- PlainSocketFactory.getSocketFactory(), 80));
- schemeRegistry.register(new Scheme("https",
- socketFactoryWithCache(sessionCache), 443));
-
- ClientConnectionManager manager =
- new ThreadSafeClientConnManager(params, schemeRegistry);
-
- // We use a factory method to modify superclass initialization
- // parameters without the funny call-a-static-method dance.
- return new AndroidHttpClient(manager, params);
- }
-
- /**
- * Returns a socket factory backed by the given persistent session cache.
- *
- * @param sessionCache to retrieve sessions from, null for no cache
- */
- private static SSLSocketFactory socketFactoryWithCache(
- SSLClientSessionCache sessionCache) {
- if (sessionCache == null) {
- // Use the default factory which doesn't support persistent
- // caching.
- return SSLSocketFactory.getSocketFactory();
- }
-
- // Create a new SSL context backed by the cache.
- // TODO: Keep a weak *identity* hash map of caches to engines. In the
- // mean time, if we have two engines for the same cache, they'll still
- // share sessions but will have to do so through the persistent cache.
- SSLContextImpl sslContext = new SSLContextImpl();
- try {
- sslContext.engineInit(null, null, null, sessionCache, null);
- } catch (KeyManagementException e) {
- throw new AssertionError(e);
- }
- return new SSLSocketFactory(sslContext.engineGetSocketFactory());
- }
-
- /**
- * Create a new HttpClient with reasonable defaults (which you can update).
- * @param userAgent to report in your HTTP requests.
- * @return AndroidHttpClient for you to use for all your requests.
- */
- public static AndroidHttpClient newInstance(String userAgent) {
- return newInstance(userAgent, null /* session cache */);
- }
-
- private final HttpClient delegate;
-
- private RuntimeException mLeakedException = new IllegalStateException(
- "AndroidHttpClient created and never closed");
-
- private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
- this.delegate = new DefaultHttpClient(ccm, params) {
- @Override
- protected BasicHttpProcessor createHttpProcessor() {
- // Add interceptor to prevent making requests from main thread.
- BasicHttpProcessor processor = super.createHttpProcessor();
- processor.addRequestInterceptor(sThreadCheckInterceptor);
- processor.addRequestInterceptor(new CurlLogger());
-
- return processor;
- }
-
- @Override
- protected HttpContext createHttpContext() {
- // Same as DefaultHttpClient.createHttpContext() minus the
- // cookie store.
- HttpContext context = new BasicHttpContext();
- context.setAttribute(
- ClientContext.AUTHSCHEME_REGISTRY,
- getAuthSchemes());
- context.setAttribute(
- ClientContext.COOKIESPEC_REGISTRY,
- getCookieSpecs());
- context.setAttribute(
- ClientContext.CREDS_PROVIDER,
- getCredentialsProvider());
- return context;
- }
- };
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- if (mLeakedException != null) {
- Log.e(TAG, "Leak found", mLeakedException);
- mLeakedException = null;
- }
- }
-
- /**
- * Block this thread from executing HTTP requests.
- * Used to guard against HTTP requests blocking the main application thread.
- * @param blocked if HTTP requests run on this thread should be denied
- */
- public static void setThreadBlocked(boolean blocked) {
- sThreadBlocked.set(blocked);
- }
-
- /**
- * Modifies a request to indicate to the server that we would like a
- * gzipped response. (Uses the "Accept-Encoding" HTTP header.)
- * @param request the request to modify
- * @see #getUngzippedContent
- */
- public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
- request.addHeader("Accept-Encoding", "gzip");
- }
-
- /**
- * Gets the input stream from a response entity. If the entity is gzipped
- * then this will get a stream over the uncompressed data.
- *
- * @param entity the entity whose content should be read
- * @return the input stream to read from
- * @throws IOException
- */
- public static InputStream getUngzippedContent(HttpEntity entity)
- throws IOException {
- InputStream responseStream = entity.getContent();
- if (responseStream == null) return responseStream;
- Header header = entity.getContentEncoding();
- if (header == null) return responseStream;
- String contentEncoding = header.getValue();
- if (contentEncoding == null) return responseStream;
- if (contentEncoding.contains("gzip")) responseStream
- = new GZIPInputStream(responseStream);
- return responseStream;
- }
-
- /**
- * Release resources associated with this client. You must call this,
- * or significant resources (sockets and memory) may be leaked.
- */
- public void close() {
- if (mLeakedException != null) {
- getConnectionManager().shutdown();
- mLeakedException = null;
- }
- }
-
- public HttpParams getParams() {
- return delegate.getParams();
- }
-
- public ClientConnectionManager getConnectionManager() {
- return delegate.getConnectionManager();
- }
-
- public HttpResponse execute(HttpUriRequest request) throws IOException {
- return delegate.execute(request);
- }
-
- public HttpResponse execute(HttpUriRequest request, HttpContext context)
- throws IOException {
- return delegate.execute(request, context);
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request)
- throws IOException {
- return delegate.execute(target, request);
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request,
- HttpContext context) throws IOException {
- return delegate.execute(target, request, context);
- }
-
- public <T> T execute(HttpUriRequest request,
- ResponseHandler<? extends T> responseHandler)
- throws IOException, ClientProtocolException {
- return delegate.execute(request, responseHandler);
- }
-
- public <T> T execute(HttpUriRequest request,
- ResponseHandler<? extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- return delegate.execute(request, responseHandler, context);
- }
-
- public <T> T execute(HttpHost target, HttpRequest request,
- ResponseHandler<? extends T> responseHandler) throws IOException,
- ClientProtocolException {
- return delegate.execute(target, request, responseHandler);
- }
-
- public <T> T execute(HttpHost target, HttpRequest request,
- ResponseHandler<? extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- return delegate.execute(target, request, responseHandler, context);
- }
-
- /**
- * Compress data to send to server.
- * Creates a Http Entity holding the gzipped data.
- * The data will not be compressed if it is too short.
- * @param data The bytes to compress
- * @return Entity holding the data
- */
- public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver)
- throws IOException {
- AbstractHttpEntity entity;
- if (data.length < getMinGzipSize(resolver)) {
- entity = new ByteArrayEntity(data);
- } else {
- ByteArrayOutputStream arr = new ByteArrayOutputStream();
- OutputStream zipper = new GZIPOutputStream(arr);
- zipper.write(data);
- zipper.close();
- entity = new ByteArrayEntity(arr.toByteArray());
- entity.setContentEncoding("gzip");
- }
- return entity;
- }
-
- /**
- * Retrieves the minimum size for compressing data.
- * Shorter data will not be compressed.
- */
- public static long getMinGzipSize(ContentResolver resolver) {
- String sMinGzipBytes = Settings.Gservices.getString(resolver,
- Settings.Gservices.SYNC_MIN_GZIP_BYTES);
-
- if (!TextUtils.isEmpty(sMinGzipBytes)) {
- try {
- return Long.parseLong(sMinGzipBytes);
- } catch (NumberFormatException nfe) {
- Log.w(TAG, "Unable to parse " +
- Settings.Gservices.SYNC_MIN_GZIP_BYTES + " " +
- sMinGzipBytes, nfe);
- }
- }
- return DEFAULT_SYNC_MIN_GZIP_BYTES;
- }
-
- /* cURL logging support. */
-
- /**
- * Logging tag and level.
- */
- private static class LoggingConfiguration {
-
- private final String tag;
- private final int level;
-
- private LoggingConfiguration(String tag, int level) {
- this.tag = tag;
- this.level = level;
- }
-
- /**
- * Returns true if logging is turned on for this configuration.
- */
- private boolean isLoggable() {
- return Log.isLoggable(tag, level);
- }
-
- /**
- * Returns true if auth logging is turned on for this configuration. Can only be set on
- * insecure devices.
- */
- private boolean isAuthLoggable() {
- String secure = SystemProperties.get("ro.secure");
- return "0".equals(secure) && Log.isLoggable(tag + "-auth", level);
- }
-
- /**
- * Prints a message using this configuration.
- */
- private void println(String message) {
- Log.println(level, tag, message);
- }
- }
-
- /** cURL logging configuration. */
- private volatile LoggingConfiguration curlConfiguration;
-
- /**
- * Enables cURL request logging for this client.
- *
- * @param name to log messages with
- * @param level at which to log messages (see {@link android.util.Log})
- */
- public void enableCurlLogging(String name, int level) {
- if (name == null) {
- throw new NullPointerException("name");
- }
- if (level < Log.VERBOSE || level > Log.ASSERT) {
- throw new IllegalArgumentException("Level is out of range ["
- + Log.VERBOSE + ".." + Log.ASSERT + "]");
- }
-
- curlConfiguration = new LoggingConfiguration(name, level);
- }
-
- /**
- * Disables cURL logging for this client.
- */
- public void disableCurlLogging() {
- curlConfiguration = null;
- }
-
- /**
- * Logs cURL commands equivalent to requests.
- */
- private class CurlLogger implements HttpRequestInterceptor {
- public void process(HttpRequest request, HttpContext context)
- throws HttpException, IOException {
- LoggingConfiguration configuration = curlConfiguration;
- if (configuration != null
- && configuration.isLoggable()
- && request instanceof HttpUriRequest) {
- configuration.println(toCurl((HttpUriRequest) request,
- configuration.isAuthLoggable()));
- }
- }
- }
-
- /**
- * Generates a cURL command equivalent to the given request.
- */
- private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
- StringBuilder builder = new StringBuilder();
-
- builder.append("curl ");
-
- for (Header header: request.getAllHeaders()) {
- if (!logAuthToken
- && (header.getName().equals("Authorization") ||
- header.getName().equals("Cookie"))) {
- continue;
- }
- builder.append("--header \"");
- builder.append(header.toString().trim());
- builder.append("\" ");
- }
-
- URI uri = request.getURI();
-
- // If this is a wrapped request, use the URI from the original
- // request instead. getURI() on the wrapper seems to return a
- // relative URI. We want an absolute URI.
- if (request instanceof RequestWrapper) {
- HttpRequest original = ((RequestWrapper) request).getOriginal();
- if (original instanceof HttpUriRequest) {
- uri = ((HttpUriRequest) original).getURI();
- }
- }
-
- builder.append("\"");
- builder.append(uri);
- builder.append("\"");
-
- if (request instanceof HttpEntityEnclosingRequest) {
- HttpEntityEnclosingRequest entityRequest =
- (HttpEntityEnclosingRequest) request;
- HttpEntity entity = entityRequest.getEntity();
- if (entity != null && entity.isRepeatable()) {
- if (entity.getContentLength() < 1024) {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- entity.writeTo(stream);
- String entityString = stream.toString();
-
- // TODO: Check the content type, too.
- builder.append(" --data-ascii \"")
- .append(entityString)
- .append("\"");
- } else {
- builder.append(" [TOO MUCH DATA TO INCLUDE]");
- }
- }
- }
-
- return builder.toString();
- }
-}
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index ed6b4c2..da6af9d 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -16,6 +16,8 @@
package android.net.http;
+import com.android.common.DomainNameValidator;
+
import org.apache.harmony.xnet.provider.jsse.SSLParameters;
import java.io.IOException;
@@ -112,7 +114,7 @@ class CertificateChainValidator {
closeSocketThrowException(
sslSocket, "certificate for this site is null");
} else {
- if (!DomainNameChecker.match(currCertificate, domain)) {
+ if (!DomainNameValidator.match(currCertificate, domain)) {
String errorMessage = "certificate not for this host: " + domain;
if (HttpLog.LOGV) {
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
index 2d39e39..b8e17da 100644
--- a/core/java/android/net/http/Connection.java
+++ b/core/java/android/net/http/Connection.java
@@ -94,7 +94,6 @@ abstract class Connection {
*/
private static final String HTTP_CONNECTION = "http.connection";
- RequestQueue.ConnectionManager mConnectionManager;
RequestFeeder mRequestFeeder;
/**
@@ -104,11 +103,9 @@ abstract class Connection {
private byte[] mBuf;
protected Connection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
mContext = context;
mHost = host;
- mConnectionManager = connectionManager;
mRequestFeeder = requestFeeder;
mCanPersist = false;
@@ -124,18 +121,15 @@ abstract class Connection {
* necessary
*/
static Connection getConnection(
- Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
+ Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
if (host.getSchemeName().equals("http")) {
- return new HttpConnection(context, host, connectionManager,
- requestFeeder);
+ return new HttpConnection(context, host, requestFeeder);
}
// Otherwise, default to https
- return new HttpsConnection(context, host, connectionManager,
- requestFeeder);
+ return new HttpsConnection(context, host, proxy, requestFeeder);
}
/**
@@ -338,7 +332,7 @@ abstract class Connection {
mRequestFeeder.requeueRequest(tReq);
empty = false;
}
- if (empty) empty = mRequestFeeder.haveRequest(mHost);
+ if (empty) empty = !mRequestFeeder.haveRequest(mHost);
}
return empty;
}
diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java
index 0b30e58..32191d2 100644
--- a/core/java/android/net/http/ConnectionThread.java
+++ b/core/java/android/net/http/ConnectionThread.java
@@ -108,24 +108,11 @@ class ConnectionThread extends Thread {
if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
request.mHost + " " + request );
- HttpHost proxy = mConnectionManager.getProxyHost();
-
- HttpHost host;
- if (false) {
- // Allow https proxy
- host = proxy == null ? request.mHost : proxy;
- } else {
- // Disallow https proxy -- tmob proxy server
- // serves a request loop for https reqs
- host = (proxy == null ||
- request.mHost.getSchemeName().equals("https")) ?
- request.mHost : proxy;
- }
- mConnection = mConnectionManager.getConnection(mContext, host);
+ mConnection = mConnectionManager.getConnection(mContext,
+ request.mHost);
mConnection.processRequests(request);
if (mConnection.getCanPersist()) {
- if (!mConnectionManager.recycleConnection(host,
- mConnection)) {
+ if (!mConnectionManager.recycleConnection(mConnection)) {
mConnection.closeConnection();
}
} else {
diff --git a/core/java/android/net/http/DomainNameChecker.java b/core/java/android/net/http/DomainNameChecker.java
deleted file mode 100644
index e4c8009..0000000
--- a/core/java/android/net/http/DomainNameChecker.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * 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.
- */
-
-package android.net.http;
-
-import org.bouncycastle.asn1.x509.X509Name;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.security.cert.X509Certificate;
-import java.security.cert.CertificateParsingException;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-import java.util.Vector;
-
-/**
- * Implements basic domain-name validation as specified by RFC2818.
- *
- * {@hide}
- */
-public class DomainNameChecker {
- private static Pattern QUICK_IP_PATTERN;
- static {
- try {
- QUICK_IP_PATTERN = Pattern.compile("^[a-f0-9\\.:]+$");
- } catch (PatternSyntaxException e) {}
- }
-
- private static final int ALT_DNS_NAME = 2;
- private static final int ALT_IPA_NAME = 7;
-
- /**
- * Checks the site certificate against the domain name of the site being visited
- * @param certificate The certificate to check
- * @param thisDomain The domain name of the site being visited
- * @return True iff if there is a domain match as specified by RFC2818
- */
- public static boolean match(X509Certificate certificate, String thisDomain) {
- if (certificate == null || thisDomain == null || thisDomain.length() == 0) {
- return false;
- }
-
- thisDomain = thisDomain.toLowerCase();
- if (!isIpAddress(thisDomain)) {
- return matchDns(certificate, thisDomain);
- } else {
- return matchIpAddress(certificate, thisDomain);
- }
- }
-
- /**
- * @return True iff the domain name is specified as an IP address
- */
- private static boolean isIpAddress(String domain) {
- boolean rval = (domain != null && domain.length() != 0);
- if (rval) {
- try {
- // do a quick-dirty IP match first to avoid DNS lookup
- rval = QUICK_IP_PATTERN.matcher(domain).matches();
- if (rval) {
- rval = domain.equals(
- InetAddress.getByName(domain).getHostAddress());
- }
- } catch (UnknownHostException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "unknown host exception";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.isIpAddress(): " + errorMessage);
- }
-
- rval = false;
- }
- }
-
- return rval;
- }
-
- /**
- * Checks the site certificate against the IP domain name of the site being visited
- * @param certificate The certificate to check
- * @param thisDomain The DNS domain name of the site being visited
- * @return True iff if there is a domain match as specified by RFC2818
- */
- private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) {
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchIpAddress(): this domain: " + thisDomain);
- }
-
- try {
- Collection subjectAltNames = certificate.getSubjectAlternativeNames();
- if (subjectAltNames != null) {
- Iterator i = subjectAltNames.iterator();
- while (i.hasNext()) {
- List altNameEntry = (List)(i.next());
- if (altNameEntry != null && 2 <= altNameEntry.size()) {
- Integer altNameType = (Integer)(altNameEntry.get(0));
- if (altNameType != null) {
- if (altNameType.intValue() == ALT_IPA_NAME) {
- String altName = (String)(altNameEntry.get(1));
- if (altName != null) {
- if (HttpLog.LOGV) {
- HttpLog.v("alternative IP: " + altName);
- }
- if (thisDomain.equalsIgnoreCase(altName)) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- } catch (CertificateParsingException e) {}
-
- return false;
- }
-
- /**
- * Checks the site certificate against the DNS domain name of the site being visited
- * @param certificate The certificate to check
- * @param thisDomain The DNS domain name of the site being visited
- * @return True iff if there is a domain match as specified by RFC2818
- */
- private static boolean matchDns(X509Certificate certificate, String thisDomain) {
- boolean hasDns = false;
- try {
- Collection subjectAltNames = certificate.getSubjectAlternativeNames();
- if (subjectAltNames != null) {
- Iterator i = subjectAltNames.iterator();
- while (i.hasNext()) {
- List altNameEntry = (List)(i.next());
- if (altNameEntry != null && 2 <= altNameEntry.size()) {
- Integer altNameType = (Integer)(altNameEntry.get(0));
- if (altNameType != null) {
- if (altNameType.intValue() == ALT_DNS_NAME) {
- hasDns = true;
- String altName = (String)(altNameEntry.get(1));
- if (altName != null) {
- if (matchDns(thisDomain, altName)) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- } catch (CertificateParsingException e) {
- // one way we can get here is if an alternative name starts with
- // '*' character, which is contrary to one interpretation of the
- // spec (a valid DNS name must start with a letter); there is no
- // good way around this, and in order to be compatible we proceed
- // to check the common name (ie, ignore alternative names)
- if (HttpLog.LOGV) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "failed to parse certificate";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchDns(): " + errorMessage);
- }
- }
- }
-
- if (!hasDns) {
- X509Name xName = new X509Name(certificate.getSubjectDN().getName());
- Vector val = xName.getValues();
- Vector oid = xName.getOIDs();
- for (int i = 0; i < oid.size(); i++) {
- if (oid.elementAt(i).equals(X509Name.CN)) {
- return matchDns(thisDomain, (String)(val.elementAt(i)));
- }
- }
- }
-
- return false;
- }
-
- /**
- * @param thisDomain The domain name of the site being visited
- * @param thatDomain The domain name from the certificate
- * @return True iff thisDomain matches thatDomain as specified by RFC2818
- */
- private static boolean matchDns(String thisDomain, String thatDomain) {
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchDns():" +
- " this domain: " + thisDomain +
- " that domain: " + thatDomain);
- }
-
- if (thisDomain == null || thisDomain.length() == 0 ||
- thatDomain == null || thatDomain.length() == 0) {
- return false;
- }
-
- thatDomain = thatDomain.toLowerCase();
-
- // (a) domain name strings are equal, ignoring case: X matches X
- boolean rval = thisDomain.equals(thatDomain);
- if (!rval) {
- String[] thisDomainTokens = thisDomain.split("\\.");
- String[] thatDomainTokens = thatDomain.split("\\.");
-
- int thisDomainTokensNum = thisDomainTokens.length;
- int thatDomainTokensNum = thatDomainTokens.length;
-
- // (b) OR thatHost is a '.'-suffix of thisHost: Z.Y.X matches X
- if (thisDomainTokensNum >= thatDomainTokensNum) {
- for (int i = thatDomainTokensNum - 1; i >= 0; --i) {
- rval = thisDomainTokens[i].equals(thatDomainTokens[i]);
- if (!rval) {
- // (c) OR we have a special *-match:
- // Z.Y.X matches *.Y.X but does not match *.X
- rval = (i == 0 && thisDomainTokensNum == thatDomainTokensNum);
- if (rval) {
- rval = thatDomainTokens[0].equals("*");
- if (!rval) {
- // (d) OR we have a *-component match:
- // f*.com matches foo.com but not bar.com
- rval = domainTokenMatch(
- thisDomainTokens[0], thatDomainTokens[0]);
- }
- }
-
- break;
- }
- }
- }
- }
-
- return rval;
- }
-
- /**
- * @param thisDomainToken The domain token from the current domain name
- * @param thatDomainToken The domain token from the certificate
- * @return True iff thisDomainToken matches thatDomainToken, using the
- * wildcard match as specified by RFC2818-3.1. For example, f*.com must
- * match foo.com but not bar.com
- */
- private static boolean domainTokenMatch(String thisDomainToken, String thatDomainToken) {
- if (thisDomainToken != null && thatDomainToken != null) {
- int starIndex = thatDomainToken.indexOf('*');
- if (starIndex >= 0) {
- if (thatDomainToken.length() - 1 <= thisDomainToken.length()) {
- String prefix = thatDomainToken.substring(0, starIndex);
- String suffix = thatDomainToken.substring(starIndex + 1);
-
- return thisDomainToken.startsWith(prefix) && thisDomainToken.endsWith(suffix);
- }
- }
- }
-
- return false;
- }
-}
diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java
index 8b12d0b..6df86bf 100644
--- a/core/java/android/net/http/HttpConnection.java
+++ b/core/java/android/net/http/HttpConnection.java
@@ -35,9 +35,8 @@ import org.apache.http.params.HttpConnectionParams;
class HttpConnection extends Connection {
HttpConnection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
- super(context, host, connectionManager, requestFeeder);
+ super(context, host, requestFeeder);
}
/**
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index 8a69d0d..f735f3d 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -131,13 +131,16 @@ public class HttpsConnection extends Connection {
*/
private boolean mAborted = false;
+ // Used when connecting through a proxy.
+ private HttpHost mProxyHost;
+
/**
* Contructor for a https connection.
*/
- HttpsConnection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
+ HttpsConnection(Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
- super(context, host, connectionManager, requestFeeder);
+ super(context, host, requestFeeder);
+ mProxyHost = proxy;
}
/**
@@ -159,8 +162,7 @@ public class HttpsConnection extends Connection {
AndroidHttpClientConnection openConnection(Request req) throws IOException {
SSLSocket sslSock = null;
- HttpHost proxyHost = mConnectionManager.getProxyHost();
- if (proxyHost != null) {
+ if (mProxyHost != null) {
// If we have a proxy set, we first send a CONNECT request
// to the proxy; if the proxy returns 200 OK, we negotiate
// a secure connection to the target server via the proxy.
@@ -172,7 +174,7 @@ public class HttpsConnection extends Connection {
Socket proxySock = null;
try {
proxySock = new Socket
- (proxyHost.getHostName(), proxyHost.getPort());
+ (mProxyHost.getHostName(), mProxyHost.getPort());
proxySock.setSoTimeout(60 * 1000);
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
index 190ae7a..77cd544 100644
--- a/core/java/android/net/http/RequestHandle.java
+++ b/core/java/android/net/http/RequestHandle.java
@@ -42,15 +42,13 @@ public class RequestHandle {
private WebAddress mUri;
private String mMethod;
private Map<String, String> mHeaders;
-
private RequestQueue mRequestQueue;
-
private Request mRequest;
-
private InputStream mBodyProvider;
private int mBodyLength;
-
private int mRedirectCount = 0;
+ // Used only with synchronous requests.
+ private Connection mConnection;
private final static String AUTHORIZATION_HEADER = "Authorization";
private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
@@ -81,6 +79,19 @@ public class RequestHandle {
}
/**
+ * Creates a new request session with a given Connection. This connection
+ * is used during a synchronous load to handle this request.
+ */
+ public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ InputStream bodyProvider, int bodyLength, Request request,
+ Connection conn) {
+ this(requestQueue, url, uri, method, headers, bodyProvider, bodyLength,
+ request);
+ mConnection = conn;
+ }
+
+ /**
* Cancels this request
*/
public void cancel() {
@@ -262,6 +273,12 @@ public class RequestHandle {
mRequest.waitUntilComplete();
}
+ public void processRequest() {
+ if (mConnection != null) {
+ mConnection.processRequests(mRequest);
+ }
+ }
+
/**
* @return Digest-scheme authentication response.
*/
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
index 875caa0..84b6487 100644
--- a/core/java/android/net/http/RequestQueue.java
+++ b/core/java/android/net/http/RequestQueue.java
@@ -171,16 +171,17 @@ public class RequestQueue implements RequestFeeder {
}
public Connection getConnection(Context context, HttpHost host) {
+ host = RequestQueue.this.determineHost(host);
Connection con = mIdleCache.getConnection(host);
if (con == null) {
mTotalConnection++;
- con = Connection.getConnection(
- mContext, host, this, RequestQueue.this);
+ con = Connection.getConnection(mContext, host, mProxyHost,
+ RequestQueue.this);
}
return con;
}
- public boolean recycleConnection(HttpHost host, Connection connection) {
- return mIdleCache.cacheConnection(host, connection);
+ public boolean recycleConnection(Connection connection) {
+ return mIdleCache.cacheConnection(connection.getHost(), connection);
}
}
@@ -342,6 +343,66 @@ public class RequestQueue implements RequestFeeder {
req);
}
+ private static class SyncFeeder implements RequestFeeder {
+ // This is used in the case where the request fails and needs to be
+ // requeued into the RequestFeeder.
+ private Request mRequest;
+ SyncFeeder() {
+ }
+ public Request getRequest() {
+ Request r = mRequest;
+ mRequest = null;
+ return r;
+ }
+ public Request getRequest(HttpHost host) {
+ return getRequest();
+ }
+ public boolean haveRequest(HttpHost host) {
+ return mRequest != null;
+ }
+ public void requeueRequest(Request r) {
+ mRequest = r;
+ }
+ }
+
+ public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ EventHandler eventHandler, InputStream bodyProvider,
+ int bodyLength) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
+ }
+
+ HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
+
+ Request req = new Request(method, host, mProxyHost, uri.mPath,
+ bodyProvider, bodyLength, eventHandler, headers);
+
+ // Open a new connection that uses our special RequestFeeder
+ // implementation.
+ host = determineHost(host);
+ Connection conn = Connection.getConnection(mContext, host, mProxyHost,
+ new SyncFeeder());
+
+ // TODO: I would like to process the request here but LoadListener
+ // needs a RequestHandle to process some messages.
+ return new RequestHandle(this, url, uri, method, headers, bodyProvider,
+ bodyLength, req, conn);
+
+ }
+
+ // Chooses between the proxy and the request's host.
+ private HttpHost determineHost(HttpHost host) {
+ // There used to be a comment in ConnectionThread about t-mob's proxy
+ // being really bad about https. But, HttpsConnection actually looks
+ // for a proxy and connects through it anyway. I think that this check
+ // is still valid because if a site is https, we will use
+ // HttpsConnection rather than HttpConnection if the proxy address is
+ // not secure.
+ return (mProxyHost == null || "https".equals(host.getSchemeName()))
+ ? host : mProxyHost;
+ }
+
/**
* @return true iff there are any non-active requests pending
*/
@@ -478,6 +539,6 @@ public class RequestQueue implements RequestFeeder {
interface ConnectionManager {
HttpHost getProxyHost();
Connection getConnection(Context context, HttpHost host);
- boolean recycleConnection(HttpHost host, Connection connection);
+ boolean recycleConnection(Connection connection);
}
}
diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java
index 2788cb1..e1b9deb 100644
--- a/core/java/android/net/http/SslError.java
+++ b/core/java/android/net/http/SslError.java
@@ -20,8 +20,6 @@ import java.security.cert.X509Certificate;
/**
* One or more individual SSL errors and the associated SSL certificate
- *
- * {@hide}
*/
public class SslError {
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 7d2c698..d28148c 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -82,7 +82,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* <li><code>Result</code>, the type of the result of the background
* computation.</li>
* </ol>
- * <p>Not all types are always used by am asynchronous task. To mark a type as unused,
+ * <p>Not all types are always used by an asynchronous task. To mark a type as unused,
* simply use the type {@link Void}:</p>
* <pre>
* private class MyTask extends AsyncTask&lt;Void, Void, Void&gt; { ... }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index e9353d8..fcd8f38 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -21,7 +21,7 @@ package android.os;
*/
public class Build {
/** Value used for when a build property is unknown. */
- private static final String UNKNOWN = "unknown";
+ public static final String UNKNOWN = "unknown";
/** Either a changelist number, or a label like "M4-rc20". */
public static final String ID = getString("ro.build.id");
@@ -41,6 +41,9 @@ public class Build {
/** The name of the instruction set (CPU type + ABI convention) of native code. */
public static final String CPU_ABI = getString("ro.product.cpu.abi");
+ /** The name of the second instruction set (CPU type + ABI convention) of native code. */
+ public static final String CPU_ABI2 = getString("ro.product.cpu.abi2");
+
/** The manufacturer of the product/hardware. */
public static final String MANUFACTURER = getString("ro.product.manufacturer");
@@ -50,6 +53,15 @@ public class Build {
/** The end-user-visible name for the end product. */
public static final String MODEL = getString("ro.product.model");
+ /** @pending The system bootloader version number. */
+ public static final String BOOTLOADER = getString("ro.bootloader");
+
+ /** @pending The radio firmware version number. */
+ public static final String RADIO = getString("gsm.version.baseband");
+
+ /** @pending The device serial number. */
+ public static final String SERIAL = getString("ro.serialno");
+
/** Various version strings. */
public static class VERSION {
/**
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index b4f64b6..b33e8be 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -753,6 +753,16 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
+ * Dumps the contents of VM reference tables (e.g. JNI locals and
+ * globals) to the log file.
+ *
+ * @hide
+ */
+ public static final void dumpReferenceTables() {
+ VMDebug.dumpReferenceTables();
+ }
+
+ /**
* API for gathering and querying instruction counts.
*
* Example usage:
diff --git a/core/java/android/os/HandlerState.java b/core/java/android/os/DropBoxManager.aidl
index 0708f7d..6474ec2 100644
--- a/core/java/android/os/HandlerState.java
+++ b/core/java/android/os/DropBoxManager.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * 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.
@@ -16,18 +16,4 @@
package android.os;
-/**
- * {@hide}
- */
-public abstract class HandlerState {
- public HandlerState() {
- }
-
- public void enter(Message message) {
- }
-
- public abstract void processMessage(Message message);
-
- public void exit(Message message) {
- }
-}
+parcelable DropBoxManager.Entry;
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
new file mode 100644
index 0000000..7889a92
--- /dev/null
+++ b/core/java/android/os/DropBoxManager.java
@@ -0,0 +1,281 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxManagerService;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Enqueues chunks of data (from various sources -- application crashes, kernel
+ * log records, etc.). The queue is size bounded and will drop old data if the
+ * enqueued data exceeds the maximum size. You can think of this as a
+ * persistent, system-wide, blob-oriented "logcat".
+ *
+ * <p>You can obtain an instance of this class by calling
+ * {@link android.content.Context#getSystemService}
+ * with {@link android.content.Context#DROPBOX_SERVICE}.
+ *
+ * <p>DropBoxManager entries are not sent anywhere directly, but other system
+ * services and debugging tools may scan and upload entries for processing.
+ */
+public class DropBoxManager {
+ private static final String TAG = "DropBoxManager";
+ private final IDropBoxManagerService mService;
+
+ /** Flag value: Entry's content was deleted to save space. */
+ public static final int IS_EMPTY = 1;
+
+ /** Flag value: Content is human-readable UTF-8 text (can be combined with IS_GZIPPED). */
+ public static final int IS_TEXT = 2;
+
+ /** Flag value: Content can be decompressed with {@link java.util.zip.GZIPOutputStream}. */
+ public static final int IS_GZIPPED = 4;
+
+ /**
+ * A single entry retrieved from the drop box.
+ * This may include a reference to a stream, so you must call
+ * {@link #close()} when you are done using it.
+ */
+ public static class Entry implements Parcelable {
+ private final String mTag;
+ private final long mTimeMillis;
+
+ private final byte[] mData;
+ private final ParcelFileDescriptor mFileDescriptor;
+ private final int mFlags;
+
+ /** Create a new empty Entry with no contents. */
+ public Entry(String tag, long millis) {
+ this(tag, millis, (Object) null, IS_EMPTY);
+ }
+
+ /** Create a new Entry with plain text contents. */
+ public Entry(String tag, long millis, String text) {
+ this(tag, millis, (Object) text.getBytes(), IS_TEXT);
+ }
+
+ /**
+ * Create a new Entry with byte array contents.
+ * The data array must not be modified after creating this entry.
+ */
+ public Entry(String tag, long millis, byte[] data, int flags) {
+ this(tag, millis, (Object) data, flags);
+ }
+
+ /**
+ * Create a new Entry with streaming data contents.
+ * Takes ownership of the ParcelFileDescriptor.
+ */
+ public Entry(String tag, long millis, ParcelFileDescriptor data, int flags) {
+ this(tag, millis, (Object) data, flags);
+ }
+
+ /**
+ * Create a new Entry with the contents read from a file.
+ * The file will be read when the entry's contents are requested.
+ */
+ public Entry(String tag, long millis, File data, int flags) throws IOException {
+ this(tag, millis, (Object) ParcelFileDescriptor.open(
+ data, ParcelFileDescriptor.MODE_READ_ONLY), flags);
+ }
+
+ /** Internal constructor for CREATOR.createFromParcel(). */
+ private Entry(String tag, long millis, Object value, int flags) {
+ if (tag == null) throw new NullPointerException();
+ if (((flags & IS_EMPTY) != 0) != (value == null)) throw new IllegalArgumentException();
+
+ mTag = tag;
+ mTimeMillis = millis;
+ mFlags = flags;
+
+ if (value == null) {
+ mData = null;
+ mFileDescriptor = null;
+ } else if (value instanceof byte[]) {
+ mData = (byte[]) value;
+ mFileDescriptor = null;
+ } else if (value instanceof ParcelFileDescriptor) {
+ mData = null;
+ mFileDescriptor = (ParcelFileDescriptor) value;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /** Close the input stream associated with this entry. */
+ public void close() {
+ try { if (mFileDescriptor != null) mFileDescriptor.close(); } catch (IOException e) { }
+ }
+
+ /** @return the tag originally attached to the entry. */
+ public String getTag() { return mTag; }
+
+ /** @return time when the entry was originally created. */
+ public long getTimeMillis() { return mTimeMillis; }
+
+ /** @return flags describing the content returned by @{link #getInputStream()}. */
+ public int getFlags() { return mFlags & ~IS_GZIPPED; } // getInputStream() decompresses.
+
+ /**
+ * @param maxBytes of string to return (will truncate at this length).
+ * @return the uncompressed text contents of the entry, null if the entry is not text.
+ */
+ public String getText(int maxBytes) {
+ if ((mFlags & IS_TEXT) == 0) return null;
+ if (mData != null) return new String(mData, 0, Math.min(maxBytes, mData.length));
+
+ InputStream is = null;
+ try {
+ is = getInputStream();
+ byte[] buf = new byte[maxBytes];
+ return new String(buf, 0, Math.max(0, is.read(buf)));
+ } catch (IOException e) {
+ return null;
+ } finally {
+ try { if (is != null) is.close(); } catch (IOException e) {}
+ }
+ }
+
+ /** @return the uncompressed contents of the entry, or null if the contents were lost */
+ public InputStream getInputStream() throws IOException {
+ InputStream is;
+ if (mData != null) {
+ is = new ByteArrayInputStream(mData);
+ } else if (mFileDescriptor != null) {
+ is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
+ } else {
+ return null;
+ }
+ return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
+ }
+
+ public static final Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
+ public Entry[] newArray(int size) { return new Entry[size]; }
+ public Entry createFromParcel(Parcel in) {
+ return new Entry(
+ in.readString(), in.readLong(), in.readValue(null), in.readInt());
+ }
+ };
+
+ public int describeContents() {
+ return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mTag);
+ out.writeLong(mTimeMillis);
+ if (mFileDescriptor != null) {
+ out.writeValue(mFileDescriptor);
+ } else {
+ out.writeValue(mData);
+ }
+ out.writeInt(mFlags);
+ }
+ }
+
+ /** {@hide} */
+ public DropBoxManager(IDropBoxManagerService service) { mService = service; }
+
+ /**
+ * Create a dummy instance for testing. All methods will fail unless
+ * overridden with an appropriate mock implementation. To obtain a
+ * functional instance, use {@link android.content.Context#getSystemService}.
+ */
+ protected DropBoxManager() { mService = null; }
+
+ /**
+ * Stores human-readable text. The data may be discarded eventually (or even
+ * immediately) if space is limited, or ignored entirely if the tag has been
+ * blocked (see {@link #isTagEnabled}).
+ *
+ * @param tag describing the type of entry being stored
+ * @param data value to store
+ */
+ public void addText(String tag, String data) {
+ try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}
+ }
+
+ /**
+ * Stores binary data, which may be ignored or discarded as with {@link #addText}.
+ *
+ * @param tag describing the type of entry being stored
+ * @param data value to store
+ * @param flags describing the data
+ */
+ public void addData(String tag, byte[] data, int flags) {
+ if (data == null) throw new NullPointerException();
+ try { mService.add(new Entry(tag, 0, data, flags)); } catch (RemoteException e) {}
+ }
+
+ /**
+ * Stores the contents of a file, which may be ignored or discarded as with
+ * {@link #addText}.
+ *
+ * @param tag describing the type of entry being stored
+ * @param file to read from
+ * @param flags describing the data
+ * @throws IOException if the file can't be opened
+ */
+ public void addFile(String tag, File file, int flags) throws IOException {
+ if (file == null) throw new NullPointerException();
+ Entry entry = new Entry(tag, 0, file, flags);
+ try {
+ mService.add(new Entry(tag, 0, file, flags));
+ } catch (RemoteException e) {
+ // ignore
+ } finally {
+ entry.close();
+ }
+ }
+
+ /**
+ * Checks any blacklists (set in system settings) to see whether a certain
+ * tag is allowed. Entries with disabled tags will be dropped immediately,
+ * so you can save the work of actually constructing and sending the data.
+ *
+ * @param tag that would be used in {@link #addText} or {@link #addFile}
+ * @return whether events with that tag would be accepted
+ */
+ public boolean isTagEnabled(String tag) {
+ try { return mService.isTagEnabled(tag); } catch (RemoteException e) { return false; }
+ }
+
+ /**
+ * Gets the next entry from the drop box *after* the specified time.
+ * Requires android.permission.READ_LOGS. You must always call
+ * {@link Entry#close()} on the return value!
+ *
+ * @param tag of entry to look for, null for all tags
+ * @param msec time of the last entry seen
+ * @return the next entry, or null if there are no more entries
+ */
+ public Entry getNextEntry(String tag, long msec) {
+ try { return mService.getNextEntry(tag, msec); } catch (RemoteException e) { return null; }
+ }
+
+ // TODO: It may be useful to have some sort of notification mechanism
+ // when data is added to the dropbox, for demand-driven readers --
+ // for now readers need to poll the dropbox to find new data.
+}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index f761e8e..9491bd4 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,8 @@ package android.os;
import java.io.File;
+import android.os.IMountService;
+
/**
* Provides access to environment variables.
*/
@@ -26,6 +28,10 @@ public class Environment {
private static final File ROOT_DIRECTORY
= getDirectory("ANDROID_ROOT", "/system");
+ private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
+
+ private static IMountService mMntSvc = null;
+
/**
* Gets the Android root directory.
*/
@@ -33,9 +39,55 @@ public class Environment {
return ROOT_DIRECTORY;
}
+ /**
+ * Gets the system directory available for secure storage.
+ * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system).
+ * Otherwise, it returns the unencrypted /data/system directory.
+ * @return File object representing the secure storage system directory.
+ * @hide
+ */
+ public static File getSystemSecureDirectory() {
+ if (isEncryptedFilesystemEnabled()) {
+ return new File(SECURE_DATA_DIRECTORY, "system");
+ } else {
+ return new File(DATA_DIRECTORY, "system");
+ }
+ }
+
+ /**
+ * Gets the data directory for secure storage.
+ * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure).
+ * Otherwise, it returns the unencrypted /data directory.
+ * @return File object representing the data directory for secure storage.
+ * @hide
+ */
+ public static File getSecureDataDirectory() {
+ if (isEncryptedFilesystemEnabled()) {
+ return SECURE_DATA_DIRECTORY;
+ } else {
+ return DATA_DIRECTORY;
+ }
+ }
+
+ /**
+ * Returns whether the Encrypted File System feature is enabled on the device or not.
+ * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code>
+ * if disabled.
+ * @hide
+ */
+ public static boolean isEncryptedFilesystemEnabled() {
+ return SystemProperties.getBoolean(SYSTEM_PROPERTY_EFS_ENABLED, false);
+ }
+
private static final File DATA_DIRECTORY
= getDirectory("ANDROID_DATA", "/data");
+ /**
+ * @hide
+ */
+ private static final File SECURE_DATA_DIRECTORY
+ = getDirectory("ANDROID_SECURE_DATA", "/data/secure");
+
private static final File EXTERNAL_STORAGE_DIRECTORY
= getDirectory("EXTERNAL_STORAGE", "/sdcard");
@@ -119,9 +171,19 @@ public class Environment {
/**
* Gets the current state of the external storage device.
+ * Note: This call should be deprecated as it doesn't support
+ * multiple volumes.
*/
public static String getExternalStorageState() {
- return SystemProperties.get("EXTERNAL_STORAGE_STATE", MEDIA_REMOVED);
+ try {
+ if (mMntSvc == null) {
+ mMntSvc = IMountService.Stub.asInterface(ServiceManager
+ .getService("mount"));
+ }
+ return mMntSvc.getVolumeState(getExternalStorageDirectory().toString());
+ } catch (Exception rex) {
+ return Environment.MEDIA_REMOVED;
+ }
}
static File getDirectory(String variableName, String defaultPath) {
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
index 38d252e..3457815 100644
--- a/core/java/android/os/FileObserver.java
+++ b/core/java/android/os/FileObserver.java
@@ -103,9 +103,7 @@ public abstract class FileObserver {
try {
observer.onEvent(mask, path);
} catch (Throwable throwable) {
- Log.e(LOG_TAG, "Unhandled throwable " + throwable.toString() +
- " (returned by observer " + observer + ")", throwable);
- RuntimeInit.crash("FileObserver", throwable);
+ Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable);
}
}
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 51dfb5b..4780cf3 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -153,14 +153,16 @@ public class FileUtils
public static String readTextFile(File file, int max, String ellipsis) throws IOException {
InputStream input = new FileInputStream(file);
try {
- if (max > 0) { // "head" mode: read the first N bytes
+ long size = file.length();
+ if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
+ if (size > 0 && (max == 0 || size < max)) max = (int) size;
byte[] data = new byte[max + 1];
int length = input.read(data);
if (length <= 0) return "";
if (length <= max) return new String(data, 0, length);
if (ellipsis == null) return new String(data, 0, max);
return new String(data, 0, max) + ellipsis;
- } else if (max < 0) { // "tail" mode: read it all, keep the last N
+ } else if (max < 0) { // "tail" mode: keep the last N
int len;
boolean rolled = false;
byte[] last = null, data = null;
@@ -180,7 +182,7 @@ public class FileUtils
}
if (ellipsis == null || !rolled) return new String(last);
return ellipsis + new String(last);
- } else { // "cat" mode: read it all
+ } else { // "cat" mode: size unknown, read it all in streaming fashion
ByteArrayOutputStream contents = new ByteArrayOutputStream();
int len;
byte[] data = new byte[1024];
diff --git a/core/java/android/os/HandlerStateMachine.java b/core/java/android/os/HandlerStateMachine.java
deleted file mode 100644
index 9e7902b..0000000
--- a/core/java/android/os/HandlerStateMachine.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * 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.os;
-
-import android.util.Log;
-import android.util.LogPrinter;
-
-/**
- * {@hide}
- *
- * Implement a state machine where each state is an object,
- * HandlerState. Each HandlerState must implement processMessage
- * and optionally enter/exit. When a state machine is created
- * the initial state must be set. When messages are sent to
- * a state machine the current state's processMessage method is
- * invoked. If this is the first message for this state the
- * enter method is called prior to processMessage and when
- * transtionTo is invoked the state's exit method will be
- * called after returning from processMessage.
- *
- * If a message should be handled in a different state the
- * processMessage method may call deferMessage. This causes
- * the message to be saved on a list until transitioning
- * to a new state, at which time all of the deferred messages
- * will be put on the front of the state machines queue and
- * processed by the new current state's processMessage
- * method.
- *
- * Below is an example state machine with two state's, S1 and S2.
- * The initial state is S1 which defers all messages and only
- * transition to S2 when message.what == TEST_WHAT_2. State S2
- * will process each messages until it receives TEST_WHAT_2
- * where it will transition back to S1:
-<code>
- class StateMachine1 extends HandlerStateMachine {
- private static final int TEST_WHAT_1 = 1;
- private static final int TEST_WHAT_2 = 2;
-
- StateMachine1(String name) {
- super(name);
- setInitialState(mS1);
- }
-
- class S1 extends HandlerState {
- &amp;#064;Override public void enter(Message message) {
- }
-
- &amp;#064;Override public void processMessage(Message message) {
- deferMessage(message);
- if (message.what == TEST_WHAT_2) {
- transitionTo(mS2);
- }
- }
-
- &amp;#064;Override public void exit(Message message) {
- }
- }
-
- class S2 extends HandlerState {
- &amp;#064;Override public void processMessage(Message message) {
- // Do some processing
- if (message.what == TEST_WHAT_2) {
- transtionTo(mS1);
- }
- }
- }
-
- private S1 mS1 = new S1();
- private S2 mS2 = new S2();
- }
-</code>
- */
-public class HandlerStateMachine {
-
- private boolean mDbg = false;
- private static final String TAG = "HandlerStateMachine";
- private String mName;
- private SmHandler mHandler;
- private HandlerThread mHandlerThread;
-
- /**
- * Handle messages sent to the state machine by calling
- * the current state's processMessage. It also handles
- * the enter/exit calls and placing any deferred messages
- * back onto the queue when transitioning to a new state.
- */
- class SmHandler extends Handler {
-
- SmHandler(Looper looper) {
- super(looper);
- }
-
- /**
- * This will dispatch the message to the
- * current state's processMessage.
- */
- @Override
- final public void handleMessage(Message msg) {
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage E");
- if (mDestState != null) {
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter");
- mCurrentState = mDestState;
- mDestState = null;
- mCurrentState.enter(msg);
- }
- if (mCurrentState != null) {
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage");
- mCurrentState.processMessage(msg);
- } else {
- /* Strange no state to execute */
- Log.e(TAG, "handleMessage: no current state, did you call setInitialState");
- }
-
- if (mDestState != null) {
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit");
- mCurrentState.exit(msg);
-
- /**
- * Place the messages from the deferred queue:t
- * on to the Handler's message queue in the
- * same order that they originally arrived.
- *
- * We set cur.when = 0 to circumvent the check
- * that this message has already been sent.
- */
- while (mDeferredMessages != null) {
- Message cur = mDeferredMessages;
- mDeferredMessages = mDeferredMessages.next;
- cur.when = 0;
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what="
- + cur.what + " target=" + cur.target);
- sendMessageAtFrontOfQueue(cur);
- }
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage X");
- }
- }
-
- public HandlerState mCurrentState;
- public HandlerState mDestState;
- public Message mDeferredMessages;
- }
-
- /**
- * Create an active StateMachine, one that has a
- * dedicated thread/looper/queue.
- */
- public HandlerStateMachine(String name) {
- mName = name;
- mHandlerThread = new HandlerThread(name);
- mHandlerThread.start();
- mHandler = new SmHandler(mHandlerThread.getLooper());
- }
-
- /**
- * Get a message and set Message.target = this.
- */
- public final Message obtainMessage()
- {
- Message msg = Message.obtain(mHandler);
- if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target);
- return msg;
- }
-
- /**
- * Get a message and set Message.target = this and
- * Message.what = what.
- */
- public final Message obtainMessage(int what) {
- Message msg = Message.obtain(mHandler, what);
- if (mDbg) {
- Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what +
- " target=" + msg.target);
- }
- return msg;
- }
-
- /**
- * Enqueue a message to this state machine.
- */
- public final void sendMessage(Message msg) {
- if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what);
- mHandler.sendMessage(msg);
- }
-
- /**
- * Enqueue a message to this state machine after a delay.
- */
- public final void sendMessageDelayed(Message msg, long delayMillis) {
- if (mDbg) {
- Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what="
- + msg.what + " delay=" + delayMillis);
- }
- mHandler.sendMessageDelayed(msg, delayMillis);
- }
-
- /**
- * Set the initial state. This must be invoked before
- * and messages are sent to the state machine.
- */
- public void setInitialState(HandlerState initialState) {
- if (mDbg) {
- Log.d(TAG, "StateMachine.setInitialState EX initialState"
- + initialState.getClass().getName());
- }
- mHandler.mDestState = initialState;
- }
-
- /**
- * transition to destination state. Upon returning
- * from processMessage the current state's exit will
- * be executed and upon the next message arriving
- * destState.enter will be invoked.
- */
- final public void transitionTo(HandlerState destState) {
- if (mDbg) {
- Log.d(TAG, "StateMachine.transitionTo EX destState"
- + destState.getClass().getName());
- }
- mHandler.mDestState = destState;
- }
-
- /**
- * Defer this message until next state transition.
- * Upon transitioning all deferred messages will be
- * placed on the queue and reprocessed in the original
- * order. (i.e. The next state the oldest messages will
- * be processed first)
- */
- final public void deferMessage(Message msg) {
- if (mDbg) {
- Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages="
- + mHandler.mDeferredMessages);
- }
-
- /* Copy the "msg" to "newMsg" as "msg" will be recycled */
- Message newMsg = obtainMessage();
- newMsg.copyFrom(msg);
-
- /* Place on front of queue */
- newMsg.next = mHandler.mDeferredMessages;
- mHandler.mDeferredMessages = newMsg;
- }
-
- /**
- * @return the name
- */
- public String getName() {
- return mName;
- }
-
- /**
- * @return Handler
- */
- public Handler getHandler() {
- return mHandler;
- }
-
- /**
- * @return if debugging is enabled
- */
- public boolean isDbg() {
- return mDbg;
- }
-
- /**
- * Set debug enable/disabled.
- */
- public void setDbg(boolean dbg) {
- mDbg = dbg;
- if (mDbg) {
- mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG));
- } else {
- mHandlerThread.getLooper().setMessageLogging(null);
- }
- }
-}
diff --git a/core/java/android/os/Hardware.java b/core/java/android/os/Hardware.java
deleted file mode 100644
index efc5617..0000000
--- a/core/java/android/os/Hardware.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.os;
-
-/**
- * {@hide}
- */
-public class Hardware
-{
-
-
- /* ********************************************************************************
- *
- *
- *
- *
- *
- *
- *
- *
- * Don't add anything else to this class. Add it to HardwareService instead.
- *
- *
- *
- *
- *
- *
- *
- * ********************************************************************************/
-
-
- public static native boolean getFlashlightEnabled();
- public static native void setFlashlightEnabled(boolean on);
- public static native void enableCameraFlash(int milliseconds);
-}
diff --git a/core/java/android/os/ICheckinService.aidl b/core/java/android/os/ICheckinService.aidl
index e56b55d..e5609b0 100644
--- a/core/java/android/os/ICheckinService.aidl
+++ b/core/java/android/os/ICheckinService.aidl
@@ -26,24 +26,12 @@ import android.os.IParentalControlCallback;
* {@hide}
*/
interface ICheckinService {
- /** Synchronously attempt a checkin with the server, return true
- * on success.
- * @throws IllegalStateException whenever an error occurs. The
- * cause of the exception will be the real exception:
- * IOException for network errors, JSONException for invalid
- * server responses, etc.
- */
- boolean checkin();
-
- /** Direct submission of crash data; returns after writing the crash. */
- void reportCrashSync(in byte[] crashData);
-
- /** Asynchronous "fire and forget" version of crash reporting. */
- oneway void reportCrashAsync(in byte[] crashData);
-
/** Reboot into the recovery system and wipe all user data. */
void masterClear();
+ /** Reboot into the recovery system, wipe all user data and enable Encrypted File Systems. */
+ void masterClearAndToggleEFS(boolean efsEnabled);
+
/**
* Determine if the device is under parental control. Return null if
* we are unable to check the parental control status.
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index 4491a8a..e73569a 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -42,17 +42,17 @@ interface IMountService
/**
* Mount external storage at given mount point.
*/
- void mountMedia(String mountPoint);
+ void mountVolume(String mountPoint);
/**
* Safely unmount external storage at given mount point.
*/
- void unmountMedia(String mountPoint);
+ void unmountVolume(String mountPoint);
/**
* Format external storage given a mount point.
*/
- void formatMedia(String mountPoint);
+ void formatVolume(String mountPoint);
/**
* Returns true if media notification sounds are enabled.
@@ -65,16 +65,43 @@ interface IMountService
void setPlayNotificationSounds(boolean value);
/**
- * Returns true if USB Mass Storage is automatically started
- * when a UMS host is detected.
+ * Gets the state of an volume via it's mountpoint.
*/
- boolean getAutoStartUms();
+ String getVolumeState(String mountPoint);
+
+ /*
+ * Creates a secure container with the specified parameters.
+ * On success, the filesystem container-path is returned.
+ */
+ String createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid);
+
+ /*
+ * Finalize a container which has just been created and populated.
+ * After finalization, the container is immutable.
+ */
+ void finalizeSecureContainer(String id);
+
+ /*
+ * Destroy a secure container, and free up all resources associated with it.
+ * NOTE: Ensure all references are released prior to deleting.
+ */
+ void destroySecureContainer(String id);
+
+ /*
+ * Mount a secure container with the specified key and owner UID.
+ * On success, the filesystem container-path is returned.
+ */
+ String mountSecureContainer(String id, String key, int ownerUid);
+
+ /*
+ * Returns the filesystem path of a mounted secure container.
+ */
+ String getSecureContainerPath(String id);
/**
- * Sets whether or not USB Mass Storage is automatically started
- * when a UMS host is detected.
+ * Gets an Array of currently known secure container IDs
*/
- void setAutoStartUms(boolean value);
+ String[] getSecureContainerList();
/**
* Shuts down the MountService and gracefully unmounts all external media.
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index b9dc860..23762ca 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -28,11 +28,12 @@ interface IPowerManager
void setPokeLock(int pokey, IBinder lock, String tag);
int getSupportedWakeLockFlags();
void setStayOnSetting(int val);
- long getScreenOnTime();
void preventScreenOn(boolean prevent);
- void setScreenBrightnessOverride(int brightness);
boolean isScreenOn();
+ void reboot(String reason);
+ void crash(String message);
// sets the brightness of the backlights (screen, keyboard, button) 0-255
void setBacklightBrightness(int brightness);
+ void setAttentionLight(boolean on, int color);
}
diff --git a/core/java/android/os/IHardwareService.aidl b/core/java/android/os/IVibratorService.aidl
index 34f30a7..c98fb56 100755
--- a/core/java/android/os/IHardwareService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -17,19 +17,10 @@
package android.os;
/** {@hide} */
-interface IHardwareService
+interface IVibratorService
{
- // Vibrator support
void vibrate(long milliseconds, IBinder token);
void vibratePattern(in long[] pattern, int repeat, IBinder token);
void cancelVibrate(IBinder token);
-
- // flashlight support
- boolean getFlashlightEnabled();
- void setFlashlightEnabled(boolean on);
- void enableCameraFlash(int milliseconds);
-
- // for the phone
- void setAttentionLight(boolean on, int color);
}
diff --git a/core/java/android/os/LocalPowerManager.java b/core/java/android/os/LocalPowerManager.java
index 3fe21d9..d348f07 100644
--- a/core/java/android/os/LocalPowerManager.java
+++ b/core/java/android/os/LocalPowerManager.java
@@ -44,7 +44,10 @@ public interface LocalPowerManager {
void enableUserActivity(boolean enabled);
// the same as the method on PowerManager
- public void userActivity(long time, boolean noChangeLights, int eventType);
+ void userActivity(long time, boolean noChangeLights, int eventType);
boolean isScreenOn();
+
+ void setScreenBrightnessOverride(int brightness);
+ void setButtonBrightnessOverride(int brightness);
}
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
index 03542dd..9742b05 100644
--- a/core/java/android/os/MemoryFile.java
+++ b/core/java/android/os/MemoryFile.java
@@ -52,7 +52,7 @@ public class MemoryFile
private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
- private static native int native_get_mapped_size(FileDescriptor fd) throws IOException;
+ private static native int native_get_size(FileDescriptor fd) throws IOException;
private FileDescriptor mFD; // ashmem file descriptor
private int mAddress; // address of ashmem memory
@@ -300,20 +300,19 @@ public class MemoryFile
* @hide
*/
public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
- return (native_get_mapped_size(fd) >= 0);
+ return (native_get_size(fd) >= 0);
}
/**
- * Returns the size of the memory file, rounded up to a page boundary, that
- * the file descriptor refers to, or -1 if the file descriptor does not
- * refer to a memory file.
+ * Returns the size of the memory file that the file descriptor refers to,
+ * or -1 if the file descriptor does not refer to a memory file.
*
* @throws IOException If <code>fd</code> is not a valid file descriptor.
*
* @hide
*/
- public static int getMappedSize(FileDescriptor fd) throws IOException {
- return native_get_mapped_size(fd);
+ public static int getSize(FileDescriptor fd) throws IOException {
+ return native_get_size(fd);
}
/**
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index caf0923..bc653d6 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -115,9 +115,7 @@ public class MessageQueue {
didIdle = true;
keep = ((IdleHandler)idler).queueIdle();
} catch (Throwable t) {
- Log.e("MessageQueue",
- "IdleHandler threw exception", t);
- RuntimeInit.crash("MessageQueue", t);
+ Log.wtf("MessageQueue", "IdleHandler threw exception", t);
}
if (!keep) {
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4b3b6f6..e4eaf45 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -326,12 +326,11 @@ public class PowerManager
{
synchronized (mToken) {
if (mHeld) {
+ Log.wtf(TAG, "WakeLock finalized while still held: " + mTag);
try {
mService.releaseWakeLock(mToken, 0);
} catch (RemoteException e) {
}
- RuntimeInit.crash(TAG, new Exception(
- "WakeLock finalized while still held: "+mTag));
}
}
}
@@ -465,6 +464,22 @@ public class PowerManager
}
}
+ /**
+ * Reboot the device. Will not return if the reboot is
+ * successful. Requires the {@link android.Manifest.permission#REBOOT}
+ * permission.
+ *
+ * @param reason code to pass to the kernel (e.g., "recovery") to
+ * request special boot modes, or null.
+ */
+ public void reboot(String reason)
+ {
+ try {
+ mService.reboot(reason);
+ } catch (RemoteException e) {
+ }
+ }
+
private PowerManager()
{
}
@@ -488,4 +503,3 @@ public class PowerManager
IPowerManager mService;
Handler mHandler;
}
-
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
new file mode 100644
index 0000000..3dd3918
--- /dev/null
+++ b/core/java/android/os/RecoverySystem.java
@@ -0,0 +1,418 @@
+/*
+ * 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.os;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.apache.harmony.security.asn1.BerInputStream;
+import org.apache.harmony.security.pkcs7.ContentInfo;
+import org.apache.harmony.security.pkcs7.SignedData;
+import org.apache.harmony.security.pkcs7.SignerInfo;
+import org.apache.harmony.security.provider.cert.X509CertImpl;
+
+/**
+ * RecoverySystem contains methods for interacting with the Android
+ * recovery system (the separate partition that can be used to install
+ * system updates, wipe user data, etc.)
+ */
+public class RecoverySystem {
+ private static final String TAG = "RecoverySystem";
+
+ /**
+ * Default location of zip file containing public keys (X509
+ * certs) authorized to sign OTA updates.
+ */
+ private static final File DEFAULT_KEYSTORE =
+ new File("/system/etc/security/otacerts.zip");
+
+ /** Send progress to listeners no more often than this (in ms). */
+ private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
+
+ /** Used to communicate with recovery. See bootable/recovery/recovery.c. */
+ private static File RECOVERY_DIR = new File("/cache/recovery");
+ private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
+ private static File LOG_FILE = new File(RECOVERY_DIR, "log");
+
+ // Length limits for reading files.
+ private static int LOG_FILE_MAX_LENGTH = 8 * 1024;
+
+ /**
+ * Interface definition for a callback to be invoked regularly as
+ * verification proceeds.
+ */
+ public interface ProgressListener {
+ /**
+ * Called periodically as the verification progresses.
+ *
+ * @param progress the approximate percentage of the
+ * verification that has been completed, ranging from 0
+ * to 100 (inclusive).
+ */
+ public void onProgress(int progress);
+ }
+
+ /** @return the set of certs that can be used to sign an OTA package. */
+ private static HashSet<Certificate> getTrustedCerts(File keystore)
+ throws IOException, GeneralSecurityException {
+ HashSet<Certificate> trusted = new HashSet<Certificate>();
+ if (keystore == null) {
+ keystore = DEFAULT_KEYSTORE;
+ }
+ ZipFile zip = new ZipFile(keystore);
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ trusted.add(cf.generateCertificate(zip.getInputStream(entry)));
+ }
+ } finally {
+ zip.close();
+ }
+ return trusted;
+ }
+
+ /**
+ * Verify the cryptographic signature of a system update package
+ * before installing it. Note that the package is also verified
+ * separately by the installer once the device is rebooted into
+ * the recovery system. This function will return only if the
+ * package was successfully verified; otherwise it will throw an
+ * exception.
+ *
+ * Verification of a package can take significant time, so this
+ * function should not be called from a UI thread.
+ *
+ * @param packageFile the package to be verified
+ * @param listener an object to receive periodic progress
+ * updates as verification proceeds. May be null.
+ * @param deviceCertsZipFile the zip file of certificates whose
+ * public keys we will accept. Verification succeeds if the
+ * package is signed by the private key corresponding to any
+ * public key in this file. May be null to use the system default
+ * file (currently "/system/etc/security/otacerts.zip").
+ *
+ * @throws IOException if there were any errors reading the
+ * package or certs files.
+ * @throws GeneralSecurityException if verification failed
+ */
+ public static void verifyPackage(File packageFile,
+ ProgressListener listener,
+ File deviceCertsZipFile)
+ throws IOException, GeneralSecurityException {
+ long fileLen = packageFile.length();
+
+ RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
+ try {
+ int lastPercent = 0;
+ long lastPublishTime = System.currentTimeMillis();
+ if (listener != null) {
+ listener.onProgress(lastPercent);
+ }
+
+ raf.seek(fileLen - 6);
+ byte[] footer = new byte[6];
+ raf.readFully(footer);
+
+ if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
+ throw new SignatureException("no signature in file (no footer)");
+ }
+
+ int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
+ int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
+ Log.v(TAG, String.format("comment size %d; signature start %d",
+ commentSize, signatureStart));
+
+ byte[] eocd = new byte[commentSize + 22];
+ raf.seek(fileLen - (commentSize + 22));
+ raf.readFully(eocd);
+
+ // Check that we have found the start of the
+ // end-of-central-directory record.
+ if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
+ eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
+ throw new SignatureException("no signature in file (bad footer)");
+ }
+
+ for (int i = 4; i < eocd.length-3; ++i) {
+ if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
+ eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
+ throw new SignatureException("EOCD marker found after start of EOCD");
+ }
+ }
+
+ // The following code is largely copied from
+ // JarUtils.verifySignature(). We could just *call* that
+ // method here if that function didn't read the entire
+ // input (ie, the whole OTA package) into memory just to
+ // compute its message digest.
+
+ BerInputStream bis = new BerInputStream(
+ new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
+ ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
+ SignedData signedData = info.getSignedData();
+ if (signedData == null) {
+ throw new IOException("signedData is null");
+ }
+ Collection encCerts = signedData.getCertificates();
+ if (encCerts.isEmpty()) {
+ throw new IOException("encCerts is empty");
+ }
+ // Take the first certificate from the signature (packages
+ // should contain only one).
+ Iterator it = encCerts.iterator();
+ X509Certificate cert = null;
+ if (it.hasNext()) {
+ cert = new X509CertImpl((org.apache.harmony.security.x509.Certificate)it.next());
+ } else {
+ throw new SignatureException("signature contains no certificates");
+ }
+
+ List sigInfos = signedData.getSignerInfos();
+ SignerInfo sigInfo;
+ if (!sigInfos.isEmpty()) {
+ sigInfo = (SignerInfo)sigInfos.get(0);
+ } else {
+ throw new IOException("no signer infos!");
+ }
+
+ // Check that the public key of the certificate contained
+ // in the package equals one of our trusted public keys.
+
+ HashSet<Certificate> trusted = getTrustedCerts(
+ deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
+
+ PublicKey signatureKey = cert.getPublicKey();
+ boolean verified = false;
+ for (Certificate c : trusted) {
+ if (c.getPublicKey().equals(signatureKey)) {
+ verified = true;
+ break;
+ }
+ }
+ if (!verified) {
+ throw new SignatureException("signature doesn't match any trusted key");
+ }
+
+ // The signature cert matches a trusted key. Now verify that
+ // the digest in the cert matches the actual file data.
+
+ // The verifier in recovery *only* handles SHA1withRSA
+ // signatures. SignApk.java always uses SHA1withRSA, no
+ // matter what the cert says to use. Ignore
+ // cert.getSigAlgName(), and instead use whatever
+ // algorithm is used by the signature (which should be
+ // SHA1withRSA).
+
+ String da = sigInfo.getdigestAlgorithm();
+ String dea = sigInfo.getDigestEncryptionAlgorithm();
+ String alg = null;
+ if (da == null || dea == null) {
+ // fall back to the cert algorithm if the sig one
+ // doesn't look right.
+ alg = cert.getSigAlgName();
+ } else {
+ alg = da + "with" + dea;
+ }
+ Signature sig = Signature.getInstance(alg);
+ sig.initVerify(cert);
+
+ // The signature covers all of the OTA package except the
+ // archive comment and its 2-byte length.
+ long toRead = fileLen - commentSize - 2;
+ long soFar = 0;
+ raf.seek(0);
+ byte[] buffer = new byte[4096];
+ while (soFar < toRead) {
+ int size = buffer.length;
+ if (soFar + size > toRead) {
+ size = (int)(toRead - soFar);
+ }
+ int read = raf.read(buffer, 0, size);
+ sig.update(buffer, 0, read);
+ soFar += read;
+
+ if (listener != null) {
+ long now = System.currentTimeMillis();
+ int p = (int)(soFar * 100 / toRead);
+ if (p > lastPercent &&
+ now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
+ lastPercent = p;
+ lastPublishTime = now;
+ listener.onProgress(lastPercent);
+ }
+ }
+ }
+ if (listener != null) {
+ listener.onProgress(100);
+ }
+
+ if (!sig.verify(sigInfo.getEncryptedDigest())) {
+ throw new SignatureException("signature digest verification failed");
+ }
+ } finally {
+ raf.close();
+ }
+ }
+
+ /**
+ * Reboots the device in order to install the given update
+ * package.
+ * Requires the {@link android.Manifest.permission#REBOOT}
+ * and {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM}
+ * permissions.
+ *
+ * @param context the Context to use
+ * @param packageFile the update package to install. Currently
+ * must be on the /cache or /data partitions.
+ *
+ * @throws IOException if writing the recovery command file
+ * fails, or if the reboot itself fails.
+ */
+ public static void installPackage(Context context, File packageFile)
+ throws IOException {
+ String filename = packageFile.getCanonicalPath();
+
+ if (filename.startsWith("/cache/")) {
+ filename = "CACHE:" + filename.substring(7);
+ } else if (filename.startsWith("/data/")) {
+ filename = "DATA:" + filename.substring(6);
+ } else {
+ throw new IllegalArgumentException(
+ "Must start with /cache or /data: " + filename);
+ }
+ Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
+ String arg = "--update_package=" + filename;
+ bootCommand(context, arg);
+ }
+
+ /**
+ * Reboots the device and wipes the user data partition. This is
+ * sometimes called a "factory reset", which is something of a
+ * misnomer because the system partition is not restored to its
+ * factory state.
+ * Requires the {@link android.Manifest.permission#REBOOT}
+ * and {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM}
+ * permissions.
+ *
+ * @param context the Context to use
+ *
+ * @throws IOException if writing the recovery command file
+ * fails, or if the reboot itself fails.
+ */
+ public static void rebootWipeUserData(Context context)
+ throws IOException {
+ bootCommand(context, "--wipe_data");
+ }
+
+ /**
+ * Reboot into the recovery system to wipe the /data partition and toggle
+ * Encrypted File Systems on/off.
+ * @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
+ * @throws IOException if something goes wrong.
+ *
+ * @hide
+ */
+ public static void rebootToggleEFS(Context context, boolean efsEnabled)
+ throws IOException {
+ if (efsEnabled) {
+ bootCommand(context, "--set_encrypted_filesystem=on");
+ } else {
+ bootCommand(context, "--set_encrypted_filesystem=off");
+ }
+ }
+
+ /**
+ * Reboot into the recovery system with the supplied argument.
+ * @param arg to pass to the recovery utility.
+ * @throws IOException if something goes wrong.
+ */
+ private static void bootCommand(Context context, String arg) throws IOException {
+ RECOVERY_DIR.mkdirs(); // In case we need it
+ COMMAND_FILE.delete(); // In case it's not writable
+ LOG_FILE.delete();
+
+ FileWriter command = new FileWriter(COMMAND_FILE);
+ try {
+ command.write(arg);
+ command.write("\n");
+ } finally {
+ command.close();
+ }
+
+ // Having written the command file, go ahead and reboot
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ pm.reboot("recovery");
+
+ throw new IOException("Reboot failed (no permissions?)");
+ }
+
+ /**
+ * Called after booting to process and remove recovery-related files.
+ * @return the log file from recovery, or null if none was found.
+ *
+ * @hide
+ */
+ public static String handleAftermath() {
+ // Record the tail of the LOG_FILE
+ String log = null;
+ try {
+ log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "No recovery log file");
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading recovery log", e);
+ }
+
+ // Delete everything in RECOVERY_DIR
+ String[] names = RECOVERY_DIR.list();
+ for (int i = 0; names != null && i < names.length; i++) {
+ File f = new File(RECOVERY_DIR, names[i]);
+ if (!f.delete()) {
+ Log.e(TAG, "Can't delete: " + f);
+ } else {
+ Log.i(TAG, "Deleted: " + f);
+ }
+ }
+
+ return log;
+ }
+
+ private void RecoverySystem() { } // Do not instantiate
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 51dcff1..1895cf8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -23,14 +23,14 @@ package android.os;
*/
public class Vibrator
{
- IHardwareService mService;
+ IVibratorService mService;
private final Binder mToken = new Binder();
/** @hide */
public Vibrator()
{
- mService = IHardwareService.Stub.asInterface(
- ServiceManager.getService("hardware"));
+ mService = IVibratorService.Stub.asInterface(
+ ServiceManager.getService("vibrator"));
}
/**
diff --git a/core/java/android/pim/vcard/Constants.java b/core/java/android/pim/vcard/Constants.java
deleted file mode 100644
index ca41ce5..0000000
--- a/core/java/android/pim/vcard/Constants.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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 android.pim.vcard;
-
-/**
- * Constants used in both composer and parser.
- */
-/* package */ class Constants {
-
- public static final String ATTR_TYPE = "TYPE";
-
- public static final String VERSION_V21 = "2.1";
- public static final String VERSION_V30 = "3.0";
-
- // Properties both the current (as of 2009-08-17) ContactsStruct and de-fact vCard extensions
- // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
- public static final String PROPERTY_X_AIM = "X-AIM";
- public static final String PROPERTY_X_MSN = "X-MSN";
- public static final String PROPERTY_X_YAHOO = "X-YAHOO";
- public static final String PROPERTY_X_ICQ = "X-ICQ";
- public static final String PROPERTY_X_JABBER = "X-JABBER";
- public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
- public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
- // Phone number for Skype, available as usual phone.
- public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
- // Some device emits this "X-" attribute, which is specifically invalid but should be
- // always properly accepted, and emitted in some special case (for that device/application).
- public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
-
- // How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0
- //
- // e.g.
- // 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."
- // 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."
- // 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."
- //
- // 2) has been the default of VCard exporter/importer in Android, but we can see the other
- // formats in vCard data emitted by the other softwares/devices.
- //
- // So we are currently not sure which type is the best; probably we will have to change which
- // type should be emitted depending on the device.
- public static final String ATTR_TYPE_HOME = "HOME";
- public static final String ATTR_TYPE_WORK = "WORK";
- public static final String ATTR_TYPE_FAX = "FAX";
- public static final String ATTR_TYPE_CELL = "CELL";
- public static final String ATTR_TYPE_VOICE = "VOICE";
- public static final String ATTR_TYPE_INTERNET = "INTERNET";
-
- public static final String ATTR_TYPE_PREF = "PREF";
-
- // Phone types valid in vCard and known to ContactsContract, but not so common.
- public static final String ATTR_TYPE_CAR = "CAR";
- public static final String ATTR_TYPE_ISDN = "ISDN";
- public static final String ATTR_TYPE_PAGER = "PAGER";
-
- // Phone types existing in vCard 2.1 but not known to ContactsContract.
- // TODO: should make parser make these TYPE_CUSTOM.
- public static final String ATTR_TYPE_MODEM = "MODEM";
- public static final String ATTR_TYPE_MSG = "MSG";
- public static final String ATTR_TYPE_BBS = "BBS";
- public static final String ATTR_TYPE_VIDEO = "VIDEO";
-
- // Phone types existing in the current Contacts structure but not valid in vCard (at least 2.1)
- // These types are encoded to "X-" attributes when composing vCard for now.
- // Parser passes these even if "X-" is added to the attribute.
- public static final String ATTR_TYPE_PHONE_EXTRA_OTHER = "OTHER";
- public static final String ATTR_TYPE_PHONE_EXTRA_CALLBACK = "CALLBACK";
- // TODO: may be "TYPE=COMPANY,PREF", not "COMPANY-MAIN".
- public static final String ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN = "COMPANY-MAIN";
- public static final String ATTR_TYPE_PHONE_EXTRA_RADIO = "RADIO";
- public static final String ATTR_TYPE_PHONE_EXTRA_TELEX = "TELEX";
- public static final String ATTR_TYPE_PHONE_EXTRA_TTY_TDD = "TTY-TDD";
- public static final String ATTR_TYPE_PHONE_EXTRA_ASSISTANT = "ASSISTANT";
-
- // DoCoMo specific attribute. Used with "SOUND" property, which is alternate of SORT-STRING in
- // vCard 3.0.
- public static final String ATTR_TYPE_X_IRMC_N = "X-IRMC-N";
-
- private Constants() {
- }
-} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java
new file mode 100644
index 0000000..875c29e
--- /dev/null
+++ b/core/java/android/pim/vcard/JapaneseUtils.java
@@ -0,0 +1,380 @@
+/*
+ * 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 android.pim.vcard;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TextUtils especially for Japanese.
+ */
+/* package */ class JapaneseUtils {
+ static private final Map<Character, String> sHalfWidthMap =
+ new HashMap<Character, String>();
+
+ static {
+ // There's no logical mapping rule in Unicode. Sigh.
+ sHalfWidthMap.put('\u3001', "\uFF64");
+ sHalfWidthMap.put('\u3002', "\uFF61");
+ sHalfWidthMap.put('\u300C', "\uFF62");
+ sHalfWidthMap.put('\u300D', "\uFF63");
+ sHalfWidthMap.put('\u301C', "~");
+ sHalfWidthMap.put('\u3041', "\uFF67");
+ sHalfWidthMap.put('\u3042', "\uFF71");
+ sHalfWidthMap.put('\u3043', "\uFF68");
+ sHalfWidthMap.put('\u3044', "\uFF72");
+ sHalfWidthMap.put('\u3045', "\uFF69");
+ sHalfWidthMap.put('\u3046', "\uFF73");
+ sHalfWidthMap.put('\u3047', "\uFF6A");
+ sHalfWidthMap.put('\u3048', "\uFF74");
+ sHalfWidthMap.put('\u3049', "\uFF6B");
+ sHalfWidthMap.put('\u304A', "\uFF75");
+ sHalfWidthMap.put('\u304B', "\uFF76");
+ sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
+ sHalfWidthMap.put('\u304D', "\uFF77");
+ sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
+ sHalfWidthMap.put('\u304F', "\uFF78");
+ sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
+ sHalfWidthMap.put('\u3051', "\uFF79");
+ sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
+ sHalfWidthMap.put('\u3053', "\uFF7A");
+ sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
+ sHalfWidthMap.put('\u3055', "\uFF7B");
+ sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
+ sHalfWidthMap.put('\u3057', "\uFF7C");
+ sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
+ sHalfWidthMap.put('\u3059', "\uFF7D");
+ sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
+ sHalfWidthMap.put('\u305B', "\uFF7E");
+ sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
+ sHalfWidthMap.put('\u305D', "\uFF7F");
+ sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
+ sHalfWidthMap.put('\u305F', "\uFF80");
+ sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
+ sHalfWidthMap.put('\u3061', "\uFF81");
+ sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
+ sHalfWidthMap.put('\u3063', "\uFF6F");
+ sHalfWidthMap.put('\u3064', "\uFF82");
+ sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
+ sHalfWidthMap.put('\u3066', "\uFF83");
+ sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
+ sHalfWidthMap.put('\u3068', "\uFF84");
+ sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
+ sHalfWidthMap.put('\u306A', "\uFF85");
+ sHalfWidthMap.put('\u306B', "\uFF86");
+ sHalfWidthMap.put('\u306C', "\uFF87");
+ sHalfWidthMap.put('\u306D', "\uFF88");
+ sHalfWidthMap.put('\u306E', "\uFF89");
+ sHalfWidthMap.put('\u306F', "\uFF8A");
+ sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
+ sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
+ sHalfWidthMap.put('\u3072', "\uFF8B");
+ sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
+ sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
+ sHalfWidthMap.put('\u3075', "\uFF8C");
+ sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
+ sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
+ sHalfWidthMap.put('\u3078', "\uFF8D");
+ sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
+ sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
+ sHalfWidthMap.put('\u307B', "\uFF8E");
+ sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
+ sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
+ sHalfWidthMap.put('\u307E', "\uFF8F");
+ sHalfWidthMap.put('\u307F', "\uFF90");
+ sHalfWidthMap.put('\u3080', "\uFF91");
+ sHalfWidthMap.put('\u3081', "\uFF92");
+ sHalfWidthMap.put('\u3082', "\uFF93");
+ sHalfWidthMap.put('\u3083', "\uFF6C");
+ sHalfWidthMap.put('\u3084', "\uFF94");
+ sHalfWidthMap.put('\u3085', "\uFF6D");
+ sHalfWidthMap.put('\u3086', "\uFF95");
+ sHalfWidthMap.put('\u3087', "\uFF6E");
+ sHalfWidthMap.put('\u3088', "\uFF96");
+ sHalfWidthMap.put('\u3089', "\uFF97");
+ sHalfWidthMap.put('\u308A', "\uFF98");
+ sHalfWidthMap.put('\u308B', "\uFF99");
+ sHalfWidthMap.put('\u308C', "\uFF9A");
+ sHalfWidthMap.put('\u308D', "\uFF9B");
+ sHalfWidthMap.put('\u308E', "\uFF9C");
+ sHalfWidthMap.put('\u308F', "\uFF9C");
+ sHalfWidthMap.put('\u3090', "\uFF72");
+ sHalfWidthMap.put('\u3091', "\uFF74");
+ sHalfWidthMap.put('\u3092', "\uFF66");
+ sHalfWidthMap.put('\u3093', "\uFF9D");
+ sHalfWidthMap.put('\u309B', "\uFF9E");
+ sHalfWidthMap.put('\u309C', "\uFF9F");
+ sHalfWidthMap.put('\u30A1', "\uFF67");
+ sHalfWidthMap.put('\u30A2', "\uFF71");
+ sHalfWidthMap.put('\u30A3', "\uFF68");
+ sHalfWidthMap.put('\u30A4', "\uFF72");
+ sHalfWidthMap.put('\u30A5', "\uFF69");
+ sHalfWidthMap.put('\u30A6', "\uFF73");
+ sHalfWidthMap.put('\u30A7', "\uFF6A");
+ sHalfWidthMap.put('\u30A8', "\uFF74");
+ sHalfWidthMap.put('\u30A9', "\uFF6B");
+ sHalfWidthMap.put('\u30AA', "\uFF75");
+ sHalfWidthMap.put('\u30AB', "\uFF76");
+ sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
+ sHalfWidthMap.put('\u30AD', "\uFF77");
+ sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
+ sHalfWidthMap.put('\u30AF', "\uFF78");
+ sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
+ sHalfWidthMap.put('\u30B1', "\uFF79");
+ sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
+ sHalfWidthMap.put('\u30B3', "\uFF7A");
+ sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
+ sHalfWidthMap.put('\u30B5', "\uFF7B");
+ sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
+ sHalfWidthMap.put('\u30B7', "\uFF7C");
+ sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
+ sHalfWidthMap.put('\u30B9', "\uFF7D");
+ sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
+ sHalfWidthMap.put('\u30BB', "\uFF7E");
+ sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
+ sHalfWidthMap.put('\u30BD', "\uFF7F");
+ sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
+ sHalfWidthMap.put('\u30BF', "\uFF80");
+ sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
+ sHalfWidthMap.put('\u30C1', "\uFF81");
+ sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
+ sHalfWidthMap.put('\u30C3', "\uFF6F");
+ sHalfWidthMap.put('\u30C4', "\uFF82");
+ sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
+ sHalfWidthMap.put('\u30C6', "\uFF83");
+ sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
+ sHalfWidthMap.put('\u30C8', "\uFF84");
+ sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
+ sHalfWidthMap.put('\u30CA', "\uFF85");
+ sHalfWidthMap.put('\u30CB', "\uFF86");
+ sHalfWidthMap.put('\u30CC', "\uFF87");
+ sHalfWidthMap.put('\u30CD', "\uFF88");
+ sHalfWidthMap.put('\u30CE', "\uFF89");
+ sHalfWidthMap.put('\u30CF', "\uFF8A");
+ sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
+ sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
+ sHalfWidthMap.put('\u30D2', "\uFF8B");
+ sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
+ sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
+ sHalfWidthMap.put('\u30D5', "\uFF8C");
+ sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
+ sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
+ sHalfWidthMap.put('\u30D8', "\uFF8D");
+ sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
+ sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
+ sHalfWidthMap.put('\u30DB', "\uFF8E");
+ sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
+ sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
+ sHalfWidthMap.put('\u30DE', "\uFF8F");
+ sHalfWidthMap.put('\u30DF', "\uFF90");
+ sHalfWidthMap.put('\u30E0', "\uFF91");
+ sHalfWidthMap.put('\u30E1', "\uFF92");
+ sHalfWidthMap.put('\u30E2', "\uFF93");
+ sHalfWidthMap.put('\u30E3', "\uFF6C");
+ sHalfWidthMap.put('\u30E4', "\uFF94");
+ sHalfWidthMap.put('\u30E5', "\uFF6D");
+ sHalfWidthMap.put('\u30E6', "\uFF95");
+ sHalfWidthMap.put('\u30E7', "\uFF6E");
+ sHalfWidthMap.put('\u30E8', "\uFF96");
+ sHalfWidthMap.put('\u30E9', "\uFF97");
+ sHalfWidthMap.put('\u30EA', "\uFF98");
+ sHalfWidthMap.put('\u30EB', "\uFF99");
+ sHalfWidthMap.put('\u30EC', "\uFF9A");
+ sHalfWidthMap.put('\u30ED', "\uFF9B");
+ sHalfWidthMap.put('\u30EE', "\uFF9C");
+ sHalfWidthMap.put('\u30EF', "\uFF9C");
+ sHalfWidthMap.put('\u30F0', "\uFF72");
+ sHalfWidthMap.put('\u30F1', "\uFF74");
+ sHalfWidthMap.put('\u30F2', "\uFF66");
+ sHalfWidthMap.put('\u30F3', "\uFF9D");
+ sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
+ sHalfWidthMap.put('\u30F5', "\uFF76");
+ sHalfWidthMap.put('\u30F6', "\uFF79");
+ sHalfWidthMap.put('\u30FB', "\uFF65");
+ sHalfWidthMap.put('\u30FC', "\uFF70");
+ sHalfWidthMap.put('\uFF01', "!");
+ sHalfWidthMap.put('\uFF02', "\"");
+ sHalfWidthMap.put('\uFF03', "#");
+ sHalfWidthMap.put('\uFF04', "$");
+ sHalfWidthMap.put('\uFF05', "%");
+ sHalfWidthMap.put('\uFF06', "&");
+ sHalfWidthMap.put('\uFF07', "'");
+ sHalfWidthMap.put('\uFF08', "(");
+ sHalfWidthMap.put('\uFF09', ")");
+ sHalfWidthMap.put('\uFF0A', "*");
+ sHalfWidthMap.put('\uFF0B', "+");
+ sHalfWidthMap.put('\uFF0C', ",");
+ sHalfWidthMap.put('\uFF0D', "-");
+ sHalfWidthMap.put('\uFF0E', ".");
+ sHalfWidthMap.put('\uFF0F', "/");
+ sHalfWidthMap.put('\uFF10', "0");
+ sHalfWidthMap.put('\uFF11', "1");
+ sHalfWidthMap.put('\uFF12', "2");
+ sHalfWidthMap.put('\uFF13', "3");
+ sHalfWidthMap.put('\uFF14', "4");
+ sHalfWidthMap.put('\uFF15', "5");
+ sHalfWidthMap.put('\uFF16', "6");
+ sHalfWidthMap.put('\uFF17', "7");
+ sHalfWidthMap.put('\uFF18', "8");
+ sHalfWidthMap.put('\uFF19', "9");
+ sHalfWidthMap.put('\uFF1A', ":");
+ sHalfWidthMap.put('\uFF1B', ";");
+ sHalfWidthMap.put('\uFF1C', "<");
+ sHalfWidthMap.put('\uFF1D', "=");
+ sHalfWidthMap.put('\uFF1E', ">");
+ sHalfWidthMap.put('\uFF1F', "?");
+ sHalfWidthMap.put('\uFF20', "@");
+ sHalfWidthMap.put('\uFF21', "A");
+ sHalfWidthMap.put('\uFF22', "B");
+ sHalfWidthMap.put('\uFF23', "C");
+ sHalfWidthMap.put('\uFF24', "D");
+ sHalfWidthMap.put('\uFF25', "E");
+ sHalfWidthMap.put('\uFF26', "F");
+ sHalfWidthMap.put('\uFF27', "G");
+ sHalfWidthMap.put('\uFF28', "H");
+ sHalfWidthMap.put('\uFF29', "I");
+ sHalfWidthMap.put('\uFF2A', "J");
+ sHalfWidthMap.put('\uFF2B', "K");
+ sHalfWidthMap.put('\uFF2C', "L");
+ sHalfWidthMap.put('\uFF2D', "M");
+ sHalfWidthMap.put('\uFF2E', "N");
+ sHalfWidthMap.put('\uFF2F', "O");
+ sHalfWidthMap.put('\uFF30', "P");
+ sHalfWidthMap.put('\uFF31', "Q");
+ sHalfWidthMap.put('\uFF32', "R");
+ sHalfWidthMap.put('\uFF33', "S");
+ sHalfWidthMap.put('\uFF34', "T");
+ sHalfWidthMap.put('\uFF35', "U");
+ sHalfWidthMap.put('\uFF36', "V");
+ sHalfWidthMap.put('\uFF37', "W");
+ sHalfWidthMap.put('\uFF38', "X");
+ sHalfWidthMap.put('\uFF39', "Y");
+ sHalfWidthMap.put('\uFF3A', "Z");
+ sHalfWidthMap.put('\uFF3B', "[");
+ sHalfWidthMap.put('\uFF3C', "\\");
+ sHalfWidthMap.put('\uFF3D', "]");
+ sHalfWidthMap.put('\uFF3E', "^");
+ sHalfWidthMap.put('\uFF3F', "_");
+ sHalfWidthMap.put('\uFF41', "a");
+ sHalfWidthMap.put('\uFF42', "b");
+ sHalfWidthMap.put('\uFF43', "c");
+ sHalfWidthMap.put('\uFF44', "d");
+ sHalfWidthMap.put('\uFF45', "e");
+ sHalfWidthMap.put('\uFF46', "f");
+ sHalfWidthMap.put('\uFF47', "g");
+ sHalfWidthMap.put('\uFF48', "h");
+ sHalfWidthMap.put('\uFF49', "i");
+ sHalfWidthMap.put('\uFF4A', "j");
+ sHalfWidthMap.put('\uFF4B', "k");
+ sHalfWidthMap.put('\uFF4C', "l");
+ sHalfWidthMap.put('\uFF4D', "m");
+ sHalfWidthMap.put('\uFF4E', "n");
+ sHalfWidthMap.put('\uFF4F', "o");
+ sHalfWidthMap.put('\uFF50', "p");
+ sHalfWidthMap.put('\uFF51', "q");
+ sHalfWidthMap.put('\uFF52', "r");
+ sHalfWidthMap.put('\uFF53', "s");
+ sHalfWidthMap.put('\uFF54', "t");
+ sHalfWidthMap.put('\uFF55', "u");
+ sHalfWidthMap.put('\uFF56', "v");
+ sHalfWidthMap.put('\uFF57', "w");
+ sHalfWidthMap.put('\uFF58', "x");
+ sHalfWidthMap.put('\uFF59', "y");
+ sHalfWidthMap.put('\uFF5A', "z");
+ sHalfWidthMap.put('\uFF5B', "{");
+ sHalfWidthMap.put('\uFF5C', "|");
+ sHalfWidthMap.put('\uFF5D', "}");
+ sHalfWidthMap.put('\uFF5E', "~");
+ sHalfWidthMap.put('\uFF61', "\uFF61");
+ sHalfWidthMap.put('\uFF62', "\uFF62");
+ sHalfWidthMap.put('\uFF63', "\uFF63");
+ sHalfWidthMap.put('\uFF64', "\uFF64");
+ sHalfWidthMap.put('\uFF65', "\uFF65");
+ sHalfWidthMap.put('\uFF66', "\uFF66");
+ sHalfWidthMap.put('\uFF67', "\uFF67");
+ sHalfWidthMap.put('\uFF68', "\uFF68");
+ sHalfWidthMap.put('\uFF69', "\uFF69");
+ sHalfWidthMap.put('\uFF6A', "\uFF6A");
+ sHalfWidthMap.put('\uFF6B', "\uFF6B");
+ sHalfWidthMap.put('\uFF6C', "\uFF6C");
+ sHalfWidthMap.put('\uFF6D', "\uFF6D");
+ sHalfWidthMap.put('\uFF6E', "\uFF6E");
+ sHalfWidthMap.put('\uFF6F', "\uFF6F");
+ sHalfWidthMap.put('\uFF70', "\uFF70");
+ sHalfWidthMap.put('\uFF71', "\uFF71");
+ sHalfWidthMap.put('\uFF72', "\uFF72");
+ sHalfWidthMap.put('\uFF73', "\uFF73");
+ sHalfWidthMap.put('\uFF74', "\uFF74");
+ sHalfWidthMap.put('\uFF75', "\uFF75");
+ sHalfWidthMap.put('\uFF76', "\uFF76");
+ sHalfWidthMap.put('\uFF77', "\uFF77");
+ sHalfWidthMap.put('\uFF78', "\uFF78");
+ sHalfWidthMap.put('\uFF79', "\uFF79");
+ sHalfWidthMap.put('\uFF7A', "\uFF7A");
+ sHalfWidthMap.put('\uFF7B', "\uFF7B");
+ sHalfWidthMap.put('\uFF7C', "\uFF7C");
+ sHalfWidthMap.put('\uFF7D', "\uFF7D");
+ sHalfWidthMap.put('\uFF7E', "\uFF7E");
+ sHalfWidthMap.put('\uFF7F', "\uFF7F");
+ sHalfWidthMap.put('\uFF80', "\uFF80");
+ sHalfWidthMap.put('\uFF81', "\uFF81");
+ sHalfWidthMap.put('\uFF82', "\uFF82");
+ sHalfWidthMap.put('\uFF83', "\uFF83");
+ sHalfWidthMap.put('\uFF84', "\uFF84");
+ sHalfWidthMap.put('\uFF85', "\uFF85");
+ sHalfWidthMap.put('\uFF86', "\uFF86");
+ sHalfWidthMap.put('\uFF87', "\uFF87");
+ sHalfWidthMap.put('\uFF88', "\uFF88");
+ sHalfWidthMap.put('\uFF89', "\uFF89");
+ sHalfWidthMap.put('\uFF8A', "\uFF8A");
+ sHalfWidthMap.put('\uFF8B', "\uFF8B");
+ sHalfWidthMap.put('\uFF8C', "\uFF8C");
+ sHalfWidthMap.put('\uFF8D', "\uFF8D");
+ sHalfWidthMap.put('\uFF8E', "\uFF8E");
+ sHalfWidthMap.put('\uFF8F', "\uFF8F");
+ sHalfWidthMap.put('\uFF90', "\uFF90");
+ sHalfWidthMap.put('\uFF91', "\uFF91");
+ sHalfWidthMap.put('\uFF92', "\uFF92");
+ sHalfWidthMap.put('\uFF93', "\uFF93");
+ sHalfWidthMap.put('\uFF94', "\uFF94");
+ sHalfWidthMap.put('\uFF95', "\uFF95");
+ sHalfWidthMap.put('\uFF96', "\uFF96");
+ sHalfWidthMap.put('\uFF97', "\uFF97");
+ sHalfWidthMap.put('\uFF98', "\uFF98");
+ sHalfWidthMap.put('\uFF99', "\uFF99");
+ sHalfWidthMap.put('\uFF9A', "\uFF9A");
+ sHalfWidthMap.put('\uFF9B', "\uFF9B");
+ sHalfWidthMap.put('\uFF9C', "\uFF9C");
+ sHalfWidthMap.put('\uFF9D', "\uFF9D");
+ sHalfWidthMap.put('\uFF9E', "\uFF9E");
+ sHalfWidthMap.put('\uFF9F', "\uFF9F");
+ sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
+ }
+
+ /**
+ * Return half-width version of that character if possible. Return 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) {
+ if (sHalfWidthMap.containsKey(ch)) {
+ return sHalfWidthMap.get(ch);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index e1c4b33..09ac1fd 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -1,64 +1,1911 @@
/*
* 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
+ * 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
- * limitations under the License.
+ * 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.content.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The class which lets users create their own vCard String.
+ */
+public class VCardBuilder {
+ private static final String LOG_TAG = "VCardBuilder";
+
+ // If you add the other element, please check all the columns are able to be
+ // converted to String.
+ //
+ // e.g. BLOB is not what we can handle here now.
+ private static final Set<String> sAllowedAndroidPropertySet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ Nickname.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE)));
+
+ 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;
+
+ private static final String VCARD_DATA_VCARD = "VCARD";
+ private static final String VCARD_DATA_PUBLIC = "PUBLIC";
+
+ private static final String VCARD_PARAM_SEPARATOR = ";";
+ private static final String VCARD_END_OF_LINE = "\r\n";
+ private static final String VCARD_DATA_SEPARATOR = ":";
+ private static final String VCARD_ITEM_SEPARATOR = ";";
+ 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 SHIFT_JIS = "SHIFT_JIS";
+ private static final String UTF_8 = "UTF-8";
+
+ private final int mVCardType;
+
+ private final boolean mIsV30;
+ private final boolean mIsJapaneseMobilePhone;
+ private final boolean mOnlyOneNoteFieldIsAvailable;
+ private final boolean mIsDoCoMo;
+ 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 mVCardCharsetParameter;
+
+ private StringBuilder mBuilder;
+ private boolean mEndAppended;
+
+ public VCardBuilder(final int vcardType) {
+ mVCardType = vcardType;
+
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
+ 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);
+
+ 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;
+ }
+ 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;
+ }
+ clear();
+ }
+
+ public void clear() {
+ mBuilder = new StringBuilder();
+ mEndAppended = false;
+ appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ if (mIsV30) {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
+ } else {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
+ }
+ }
+
+ private boolean containsNonEmptyName(final ContentValues contentValues) {
+ final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+ final String phoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String phoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String phoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+ return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
+ TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
+ TextUtils.isEmpty(suffix) && TextUtils.isEmpty(phoneticFamilyName) &&
+ TextUtils.isEmpty(phoneticMiddleName) && TextUtils.isEmpty(phoneticGivenName) &&
+ TextUtils.isEmpty(displayName));
+ }
+
+ private ContentValues getPrimaryContentValue(final List<ContentValues> contentValuesList) {
+ ContentValues primaryContentValues = null;
+ ContentValues subprimaryContentValues = null;
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null){
+ continue;
+ }
+ Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
+ if (isSuperPrimary != null && isSuperPrimary > 0) {
+ // We choose "super primary" ContentValues.
+ primaryContentValues = contentValues;
+ break;
+ } else if (primaryContentValues == null) {
+ // We choose the first "primary" ContentValues
+ // if "super primary" ContentValues does not exist.
+ final Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+ if (isPrimary != null && isPrimary > 0 &&
+ containsNonEmptyName(contentValues)) {
+ primaryContentValues = contentValues;
+ // Do not break, since there may be ContentValues with "super primary"
+ // afterword.
+ } else if (subprimaryContentValues == null &&
+ containsNonEmptyName(contentValues)) {
+ subprimaryContentValues = contentValues;
+ }
+ }
+ }
+
+ if (primaryContentValues == null) {
+ if (subprimaryContentValues != null) {
+ // We choose the first ContentValues if any "primary" ContentValues does not exist.
+ primaryContentValues = subprimaryContentValues;
+ } else {
+ Log.e(LOG_TAG, "All ContentValues given from database is empty.");
+ primaryContentValues = new ContentValues();
+ }
+ }
+
+ return primaryContentValues;
+ }
+
+ /**
+ * For safety, we'll emit just one value around StructuredName, as external importers
+ * may get confused with multiple "N", "FN", etc. properties, though it is valid in
+ * vCard spec.
+ */
+ public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
+ } else if (mIsV30) {
+ // vCard 3.0 requires "N" and "FN" properties.
+ appendLine(VCardConstants.PROPERTY_N, "");
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ }
+ return this;
+ }
+
+ final ContentValues contentValues = getPrimaryContentValue(contentValuesList);
+ final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+ final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+
+ if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+ final boolean reallyAppendCharsetParameterToName =
+ shouldAppendCharsetParam(familyName, givenName, middleName, prefix, suffix);
+ final boolean reallyUseQuotedPrintableToName =
+ (!mRefrainsQPToNameProperties &&
+ !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
+
+ final String formattedName;
+ if (!TextUtils.isEmpty(displayName)) {
+ formattedName = displayName;
+ } else {
+ formattedName = VCardUtils.constructNameFromElements(
+ VCardConfig.getNameOrderType(mVCardType),
+ familyName, middleName, givenName, prefix, suffix);
+ }
+ final boolean reallyAppendCharsetParameterToFN =
+ shouldAppendCharsetParam(formattedName);
+ final boolean reallyUseQuotedPrintableToFN =
+ !mRefrainsQPToNameProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
+
+ final String encodedFamily;
+ final String encodedGiven;
+ final String encodedMiddle;
+ final String encodedPrefix;
+ final String encodedSuffix;
+ if (reallyUseQuotedPrintableToName) {
+ encodedFamily = encodeQuotedPrintable(familyName);
+ encodedGiven = encodeQuotedPrintable(givenName);
+ encodedMiddle = encodeQuotedPrintable(middleName);
+ encodedPrefix = encodeQuotedPrintable(prefix);
+ encodedSuffix = encodeQuotedPrintable(suffix);
+ } else {
+ encodedFamily = escapeCharacters(familyName);
+ encodedGiven = escapeCharacters(givenName);
+ encodedMiddle = escapeCharacters(middleName);
+ encodedPrefix = escapeCharacters(prefix);
+ encodedSuffix = escapeCharacters(suffix);
+ }
+
+ final String encodedFormattedname =
+ (reallyUseQuotedPrintableToFN ?
+ encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+ if (mIsDoCoMo) {
+ if (reallyAppendCharsetParameterToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ // DoCoMo phones require that all the elements in the "family name" field.
+ mBuilder.append(formattedName);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ } else {
+ if (reallyAppendCharsetParameterToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedFamily);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedGiven);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedMiddle);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedPrefix);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedSuffix);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+
+ // FN property
+ mBuilder.append(VCardConstants.PROPERTY_FN);
+ if (reallyAppendCharsetParameterToFN) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToFN) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedFormattedname);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (!TextUtils.isEmpty(displayName)) {
+ final boolean reallyUseQuotedPrintableToDisplayName =
+ (!mRefrainsQPToNameProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
+ final String encodedDisplayName =
+ reallyUseQuotedPrintableToDisplayName ?
+ encodeQuotedPrintable(displayName) :
+ escapeCharacters(displayName);
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+ if (shouldAppendCharsetParam(displayName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToDisplayName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedDisplayName);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ 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.
+ if (shouldAppendCharsetParam(displayName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedDisplayName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (mIsV30) {
+ // vCard 3.0 specification requires these fields.
+ appendLine(VCardConstants.PROPERTY_N, "");
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ } else if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
+ }
+
+ appendPhoneticNameFields(contentValues);
+ return this;
+ }
+
+ private void appendPhoneticNameFields(final ContentValues contentValues) {
+ final String phoneticFamilyName;
+ final String phoneticMiddleName;
+ final String phoneticGivenName;
+ {
+ final String tmpPhoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String tmpPhoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String tmpPhoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ if (mNeedsToConvertPhoneticString) {
+ phoneticFamilyName = VCardUtils.toHalfWidthString(tmpPhoneticFamilyName);
+ phoneticMiddleName = VCardUtils.toHalfWidthString(tmpPhoneticMiddleName);
+ phoneticGivenName = VCardUtils.toHalfWidthString(tmpPhoneticGivenName);
+ } else {
+ phoneticFamilyName = tmpPhoneticFamilyName;
+ phoneticMiddleName = tmpPhoneticMiddleName;
+ phoneticGivenName = tmpPhoneticGivenName;
+ }
+ }
+
+ if (TextUtils.isEmpty(phoneticFamilyName)
+ && TextUtils.isEmpty(phoneticMiddleName)
+ && TextUtils.isEmpty(phoneticGivenName)) {
+ if (mIsDoCoMo) {
+ mBuilder.append(VCardConstants.PROPERTY_SOUND);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ return;
+ }
+
+ // Try to emit the field(s) related to phonetic name.
+ if (mIsV30) {
+ final String sortString = VCardUtils
+ .constructNameFromElements(mVCardType,
+ phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
+ mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
+ if (shouldAppendCharsetParam(sortString)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(escapeCharacters(sortString));
+ 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
+ // 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.
+ //
+ // 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;;;
+ mBuilder.append(VCardConstants.PROPERTY_SOUND);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+
+ boolean reallyUseQuotedPrintable =
+ (!mRefrainsQPToNameProperties
+ && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticFamilyName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticMiddleName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticGivenName)));
+
+ final String encodedPhoneticFamilyName;
+ final String encodedPhoneticMiddleName;
+ final String encodedPhoneticGivenName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+ encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+ encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+ } else {
+ encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+ encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+ encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+ }
+
+ if (shouldAppendCharsetParam(encodedPhoneticFamilyName,
+ encodedPhoneticMiddleName, encodedPhoneticGivenName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ {
+ boolean first = true;
+ if (!TextUtils.isEmpty(encodedPhoneticFamilyName)) {
+ mBuilder.append(encodedPhoneticFamilyName);
+ first = false;
+ }
+ if (!TextUtils.isEmpty(encodedPhoneticMiddleName)) {
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(' ');
+ }
+ mBuilder.append(encodedPhoneticMiddleName);
+ }
+ if (!TextUtils.isEmpty(encodedPhoneticGivenName)) {
+ if (!first) {
+ mBuilder.append(' ');
+ }
+ 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_END_OF_LINE);
+ }
+
+ if (mUsesDefactProperty) {
+ if (!TextUtils.isEmpty(phoneticGivenName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
+ final String encodedPhoneticGivenName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+ } else {
+ encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME);
+ if (shouldAppendCharsetParam(phoneticGivenName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticGivenName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ if (!TextUtils.isEmpty(phoneticMiddleName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
+ final String encodedPhoneticMiddleName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+ } else {
+ encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
+ if (shouldAppendCharsetParam(phoneticMiddleName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticMiddleName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ if (!TextUtils.isEmpty(phoneticFamilyName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
+ final String encodedPhoneticFamilyName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+ } else {
+ encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME);
+ if (shouldAppendCharsetParam(phoneticFamilyName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticFamilyName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ }
+ }
+
+ public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
+ final boolean useAndroidProperty;
+ if (mIsV30) {
+ useAndroidProperty = false;
+ } else if (mUsesAndroidProperty) {
+ useAndroidProperty = true;
+ } else {
+ // There's no way to add this field.
+ return this;
+ }
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final String nickname = contentValues.getAsString(Nickname.NAME);
+ if (TextUtils.isEmpty(nickname)) {
+ continue;
+ }
+ if (useAndroidProperty) {
+ appendAndroidSpecificProperty(Nickname.CONTENT_ITEM_TYPE, contentValues);
+ } else {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_NICKNAME, nickname);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendPhones(final List<ContentValues> contentValuesList) {
+ boolean phoneLineExists = false;
+ if (contentValuesList != null) {
+ Set<String> phoneSet = new HashSet<String>();
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
+ final String label = contentValues.getAsString(Phone.LABEL);
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Phone.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ String phoneNumber = contentValues.getAsString(Phone.NUMBER);
+ if (phoneNumber != null) {
+ phoneNumber = phoneNumber.trim();
+ }
+ if (TextUtils.isEmpty(phoneNumber)) {
+ continue;
+ }
+ int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+ if (type == Phone.TYPE_PAGER) {
+ 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);
+ if (phoneNumberList.isEmpty()) {
+ continue;
+ }
+ phoneLineExists = true;
+ for (String actualPhoneNumber : phoneNumberList) {
+ if (!phoneSet.contains(actualPhoneNumber)) {
+ final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
+ final String formattedPhoneNumber =
+ PhoneNumberUtils.formatNumber(actualPhoneNumber, format);
+ phoneSet.add(actualPhoneNumber);
+ appendTelLine(type, label, formattedPhoneNumber, isPrimary);
+ }
+ }
+ }
+ }
+ }
+
+ if (!phoneLineExists && mIsDoCoMo) {
+ appendTelLine(Phone.TYPE_HOME, "", "", false);
+ }
+
+ return this;
+ }
+
+ private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
+ 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);
+ if (Character.isDigit(ch)) {
+ builder.append(ch);
+ } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
+ phoneList.add(builder.toString());
+ builder = new StringBuilder();
+ }
+ }
+ if (builder.length() > 0) {
+ phoneList.add(builder.toString());
+ }
+
+ return phoneList;
+ }
+
+ public VCardBuilder appendEmails(final List<ContentValues> contentValuesList) {
+ boolean emailAddressExists = false;
+ if (contentValuesList != null) {
+ final Set<String> addressSet = new HashSet<String>();
+ for (ContentValues contentValues : contentValuesList) {
+ String emailAddress = contentValues.getAsString(Email.DATA);
+ if (emailAddress != null) {
+ emailAddress = emailAddress.trim();
+ }
+ if (TextUtils.isEmpty(emailAddress)) {
+ continue;
+ }
+ Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
+ final int type = (typeAsObject != null ?
+ typeAsObject : DEFAULT_EMAIL_TYPE);
+ final String label = contentValues.getAsString(Email.LABEL);
+ Integer isPrimaryAsInteger = contentValues.getAsInteger(Email.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ emailAddressExists = true;
+ if (!addressSet.contains(emailAddress)) {
+ addressSet.add(emailAddress);
+ appendEmailLine(type, label, emailAddress, isPrimary);
+ }
+ }
+ }
+
+ if (!emailAddressExists && mIsDoCoMo) {
+ appendEmailLine(Email.TYPE_HOME, "", "", false);
+ }
+
+ return this;
+ }
+
+ public VCardBuilder appendPostals(final List<ContentValues> contentValuesList) {
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ if (mIsDoCoMo) {
+ mBuilder.append(VCardConstants.PROPERTY_ADR);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_HOME);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ } else {
+ if (mIsDoCoMo) {
+ appendPostalsForDoCoMo(contentValuesList);
+ } else {
+ appendPostalsForGeneric(contentValuesList);
+ }
+ }
+
+ return this;
+ }
+
+ private static final Map<Integer, Integer> sPostalTypePriorityMap;
+
+ static {
+ sPostalTypePriorityMap = new HashMap<Integer, Integer>();
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_HOME, 0);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_WORK, 1);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_OTHER, 2);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_CUSTOM, 3);
+ }
+
+ /**
+ * Tries to append just one line. If there's no appropriate address
+ * information, append an empty line.
+ */
+ private void appendPostalsForDoCoMo(final List<ContentValues> contentValuesList) {
+ int currentPriority = Integer.MAX_VALUE;
+ int currentType = Integer.MAX_VALUE;
+ ContentValues currentContentValues = null;
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final Integer priorityAsInteger = sPostalTypePriorityMap.get(typeAsInteger);
+ final int priority =
+ (priorityAsInteger != null ? priorityAsInteger : Integer.MAX_VALUE);
+ if (priority < currentPriority) {
+ currentPriority = priority;
+ currentType = typeAsInteger;
+ currentContentValues = contentValues;
+ if (priority == 0) {
+ break;
+ }
+ }
+ }
+
+ if (currentContentValues == null) {
+ Log.w(LOG_TAG, "Should not come here. Must have at least one postal data.");
+ return;
+ }
-public interface VCardBuilder {
- void start();
+ final String label = currentContentValues.getAsString(StructuredPostal.LABEL);
+ appendPostalLine(currentType, label, currentContentValues, false, true);
+ }
- void end();
+ private void appendPostalsForGeneric(final List<ContentValues> contentValuesList) {
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final int type = (typeAsInteger != null ?
+ typeAsInteger : DEFAULT_POSTAL_TYPE);
+ final String label = contentValues.getAsString(StructuredPostal.LABEL);
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(StructuredPostal.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ appendPostalLine(type, label, contentValues, isPrimary, false);
+ }
+ }
- /**
- * BEGIN:VCARD
+ private static class PostalStruct {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressData;
+ public PostalStruct(final boolean reallyUseQuotedPrintable,
+ final boolean appendCharset, final String addressData) {
+ this.reallyUseQuotedPrintable = reallyUseQuotedPrintable;
+ this.appendCharset = appendCharset;
+ this.addressData = addressData;
+ }
+ }
+
+ /**
+ * @return null when there's no information available to construct the data.
*/
- void startRecord(String type);
+ private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ final String rawPoBox = contentValues.getAsString(StructuredPostal.POBOX);
+ final String rawNeighborhood = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
+ final String rawStreet = contentValues.getAsString(StructuredPostal.STREET);
+ final String rawLocality = contentValues.getAsString(StructuredPostal.CITY);
+ final String rawRegion = contentValues.getAsString(StructuredPostal.REGION);
+ final String rawPostalCode = contentValues.getAsString(StructuredPostal.POSTCODE);
+ final String rawCountry = contentValues.getAsString(StructuredPostal.COUNTRY);
+ final String[] rawAddressArray = new String[]{
+ rawPoBox, rawNeighborhood, rawStreet, rawLocality,
+ rawRegion, rawPostalCode, rawCountry};
+ if (!VCardUtils.areAllEmpty(rawAddressArray)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawAddressArray));
+ final boolean appendCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawAddressArray);
+ final String encodedPoBox;
+ final String encodedStreet;
+ final String encodedLocality;
+ final String encodedRegion;
+ final String encodedPostalCode;
+ final String encodedCountry;
+ final String encodedNeighborhood;
+
+ final String rawLocality2;
+ // This looks inefficient since we encode rawLocality and rawNeighborhood twice,
+ // but this is intentional.
+ //
+ // QP encoding may add line feeds when needed and the result of
+ // - encodeQuotedPrintable(rawLocality + " " + rawNeighborhood)
+ // may be different from
+ // - encodedLocality + " " + encodedNeighborhood.
+ //
+ // We use safer way.
+ if (TextUtils.isEmpty(rawLocality)) {
+ if (TextUtils.isEmpty(rawNeighborhood)) {
+ rawLocality2 = "";
+ } else {
+ rawLocality2 = rawNeighborhood;
+ }
+ } else {
+ if (TextUtils.isEmpty(rawNeighborhood)) {
+ rawLocality2 = rawLocality;
+ } else {
+ rawLocality2 = rawLocality + " " + rawNeighborhood;
+ }
+ }
+ if (reallyUseQuotedPrintable) {
+ encodedPoBox = encodeQuotedPrintable(rawPoBox);
+ encodedStreet = encodeQuotedPrintable(rawStreet);
+ encodedLocality = encodeQuotedPrintable(rawLocality2);
+ encodedRegion = encodeQuotedPrintable(rawRegion);
+ encodedPostalCode = encodeQuotedPrintable(rawPostalCode);
+ encodedCountry = encodeQuotedPrintable(rawCountry);
+ } else {
+ encodedPoBox = escapeCharacters(rawPoBox);
+ encodedStreet = escapeCharacters(rawStreet);
+ encodedLocality = escapeCharacters(rawLocality2);
+ encodedRegion = escapeCharacters(rawRegion);
+ encodedPostalCode = escapeCharacters(rawPostalCode);
+ 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);
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ } else { // VCardUtils.areAllEmpty(rawAddressArray) == true
+ // Try to use FORMATTED_ADDRESS instead.
+ final String rawFormattedAddress =
+ contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+ if (TextUtils.isEmpty(rawFormattedAddress)) {
+ return null;
+ }
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawFormattedAddress));
+ final boolean appendCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawFormattedAddress);
+ final String encodedFormattedAddress;
+ if (reallyUseQuotedPrintable) {
+ encodedFormattedAddress = encodeQuotedPrintable(rawFormattedAddress);
+ } else {
+ encodedFormattedAddress = escapeCharacters(rawFormattedAddress);
+ }
+
+ // 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);
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ }
+ }
+
+ public VCardBuilder appendIms(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer protocolAsObject = contentValues.getAsInteger(Im.PROTOCOL);
+ if (protocolAsObject == null) {
+ continue;
+ }
+ final String propertyName = VCardUtils.getPropertyNameForIm(protocolAsObject);
+ if (propertyName == null) {
+ continue;
+ }
+ String data = contentValues.getAsString(Im.DATA);
+ if (data != null) {
+ data = data.trim();
+ }
+ if (TextUtils.isEmpty(data)) {
+ continue;
+ }
+ final String typeAsString;
+ {
+ final Integer typeAsInteger = contentValues.getAsInteger(Im.TYPE);
+ switch (typeAsInteger != null ? typeAsInteger : Im.TYPE_OTHER) {
+ case Im.TYPE_HOME: {
+ typeAsString = VCardConstants.PARAM_TYPE_HOME;
+ break;
+ }
+ case Im.TYPE_WORK: {
+ typeAsString = VCardConstants.PARAM_TYPE_WORK;
+ break;
+ }
+ case Im.TYPE_CUSTOM: {
+ final String label = contentValues.getAsString(Im.LABEL);
+ typeAsString = (label != null ? "X-" + label : null);
+ break;
+ }
+ case Im.TYPE_OTHER: // Ignore
+ default: {
+ typeAsString = null;
+ break;
+ }
+ }
+ }
+
+ final List<String> parameterList = new ArrayList<String>();
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+
+ appendLineWithCharsetAndQPDetection(propertyName, parameterList, data);
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendWebsites(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ String website = contentValues.getAsString(Website.URL);
+ if (website != null) {
+ website = website.trim();
+ }
+
+ // Note: vCard 3.0 does not allow any parameter addition toward "URL"
+ // property, while there's no document in vCard 2.1.
+ if (!TextUtils.isEmpty(website)) {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_URL, website);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendOrganizations(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ String company = contentValues.getAsString(Organization.COMPANY);
+ if (company != null) {
+ company = company.trim();
+ }
+ String department = contentValues.getAsString(Organization.DEPARTMENT);
+ if (department != null) {
+ department = department.trim();
+ }
+ String title = contentValues.getAsString(Organization.TITLE);
+ if (title != null) {
+ title = title.trim();
+ }
+
+ StringBuilder orgBuilder = new StringBuilder();
+ if (!TextUtils.isEmpty(company)) {
+ orgBuilder.append(company);
+ }
+ if (!TextUtils.isEmpty(department)) {
+ if (orgBuilder.length() > 0) {
+ orgBuilder.append(';');
+ }
+ orgBuilder.append(department);
+ }
+ final String orgline = orgBuilder.toString();
+ appendLine(VCardConstants.PROPERTY_ORG, orgline,
+ !VCardUtils.containsOnlyPrintableAscii(orgline),
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
+
+ if (!TextUtils.isEmpty(title)) {
+ appendLine(VCardConstants.PROPERTY_TITLE, title,
+ !VCardUtils.containsOnlyPrintableAscii(title),
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendPhotos(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
+ if (data == null) {
+ continue;
+ }
+ final String photoType = VCardUtils.guessImageType(data);
+ if (photoType == null) {
+ Log.d(LOG_TAG, "Unknown photo type. Ignored.");
+ continue;
+ }
+ final String photoString = new String(Base64.encodeBase64(data));
+ if (!TextUtils.isEmpty(photoString)) {
+ appendPhotoLine(photoString, photoType);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendNotes(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ if (mOnlyOneNoteFieldIsAvailable) {
+ final StringBuilder noteBuilder = new StringBuilder();
+ boolean first = true;
+ for (final ContentValues contentValues : contentValuesList) {
+ String note = contentValues.getAsString(Note.NOTE);
+ if (note == null) {
+ note = "";
+ }
+ if (note.length() > 0) {
+ if (first) {
+ first = false;
+ } else {
+ noteBuilder.append('\n');
+ }
+ noteBuilder.append(note);
+ }
+ }
+ final String noteStr = noteBuilder.toString();
+ // This means we scan noteStr completely twice, which is redundant.
+ // But for now, we assume this is not so time-consuming..
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+ } else {
+ for (ContentValues contentValues : contentValuesList) {
+ final String noteStr = contentValues.getAsString(Note.NOTE);
+ if (!TextUtils.isEmpty(noteStr)) {
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+ }
+ }
+ }
+ }
+ return this;
+ }
- /** END:VXX */
- void endRecord();
+ public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ String primaryBirthday = null;
+ String secondaryBirthday = null;
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer eventTypeAsInteger = contentValues.getAsInteger(Event.TYPE);
+ final int eventType;
+ if (eventTypeAsInteger != null) {
+ eventType = eventTypeAsInteger;
+ } else {
+ eventType = Event.TYPE_OTHER;
+ }
+ if (eventType == Event.TYPE_BIRTHDAY) {
+ final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
+ if (birthdayCandidate == null) {
+ continue;
+ }
+ final Integer isSuperPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_SUPER_PRIMARY);
+ final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ?
+ (isSuperPrimaryAsInteger > 0) : false);
+ if (isSuperPrimary) {
+ // "super primary" birthday should the prefered one.
+ primaryBirthday = birthdayCandidate;
+ break;
+ }
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ // We don't break here since "super primary" birthday may exist later.
+ primaryBirthday = birthdayCandidate;
+ } else if (secondaryBirthday == null) {
+ // First entry is set to the "secondary" candidate.
+ secondaryBirthday = birthdayCandidate;
+ }
+ } else if (mUsesAndroidProperty) {
+ // Event types other than Birthday is not supported by vCard.
+ appendAndroidSpecificProperty(Event.CONTENT_ITEM_TYPE, contentValues);
+ }
+ }
+ if (primaryBirthday != null) {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+ primaryBirthday.trim());
+ } else if (secondaryBirthday != null){
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+ secondaryBirthday.trim());
+ }
+ }
+ return this;
+ }
- void startProperty();
+ public VCardBuilder appendRelation(final List<ContentValues> contentValuesList) {
+ if (mUsesAndroidProperty && contentValuesList != null) {
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ appendAndroidSpecificProperty(Relation.CONTENT_ITEM_TYPE, contentValues);
+ }
+ }
+ return this;
+ }
- void endProperty();
+ public void appendPostalLine(final int type, final String label,
+ final ContentValues contentValues,
+ final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressValue;
+ {
+ PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
+ if (postalStruct == null) {
+ if (emitLineEveryTime) {
+ reallyUseQuotedPrintable = false;
+ appendCharset = false;
+ addressValue = "";
+ } else {
+ return;
+ }
+ } else {
+ reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
+ appendCharset = postalStruct.appendCharset;
+ addressValue = postalStruct.addressData;
+ }
+ }
+
+ List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+ switch (type) {
+ case StructuredPostal.TYPE_HOME: {
+ parameterList.add(VCardConstants.PARAM_TYPE_HOME);
+ break;
+ }
+ case StructuredPostal.TYPE_WORK: {
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ break;
+ }
+ case StructuredPostal.TYPE_CUSTOM: {
+ if (!TextUtils.isEmpty(label)
+ && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ // We're not sure whether the label is valid in the spec
+ // ("IANA-token" in the vCard 3.0 is unclear...)
+ // Just for safety, we add "X-" at the beggining of each label.
+ // Also checks the label obeys with vCard 3.0 spec.
+ parameterList.add("X-" + label);
+ }
+ break;
+ }
+ case StructuredPostal.TYPE_OTHER: {
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
+ break;
+ }
+ }
+
+ mBuilder.append(VCardConstants.PROPERTY_ADR);
+ if (!parameterList.isEmpty()) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (appendCharset) {
+ // Strictly, vCard 3.0 does not allow exporters to emit charset information,
+ // but we will add it since the information should be useful for importers,
+ //
+ // Assume no parser does not emit error with this parameter in vCard 3.0.
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(addressValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendEmailLine(final int type, final String label,
+ final String rawValue, final boolean isPrimary) {
+ final String typeAsString;
+ switch (type) {
+ case Email.TYPE_CUSTOM: {
+ if (VCardUtils.isMobilePhoneLabel(label)) {
+ typeAsString = VCardConstants.PARAM_TYPE_CELL;
+ } else if (!TextUtils.isEmpty(label)
+ && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ typeAsString = "X-" + label;
+ } else {
+ typeAsString = null;
+ }
+ break;
+ }
+ case Email.TYPE_HOME: {
+ typeAsString = VCardConstants.PARAM_TYPE_HOME;
+ break;
+ }
+ case Email.TYPE_WORK: {
+ typeAsString = VCardConstants.PARAM_TYPE_WORK;
+ break;
+ }
+ case Email.TYPE_OTHER: {
+ typeAsString = null;
+ break;
+ }
+ case Email.TYPE_MOBILE: {
+ typeAsString = VCardConstants.PARAM_TYPE_CELL;
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown Email type: " + type);
+ typeAsString = null;
+ break;
+ }
+ }
+
+ final List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_EMAIL, parameterList,
+ rawValue);
+ }
+
+ public void appendTelLine(final Integer typeAsInteger, final String label,
+ final String encodedValue, boolean isPrimary) {
+ mBuilder.append(VCardConstants.PROPERTY_TEL);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+
+ final int type;
+ if (typeAsInteger == null) {
+ type = Phone.TYPE_OTHER;
+ } else {
+ type = typeAsInteger;
+ }
+
+ ArrayList<String> parameterList = new ArrayList<String>();
+ switch (type) {
+ case Phone.TYPE_HOME: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_HOME));
+ break;
+ }
+ case Phone.TYPE_WORK: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK));
+ break;
+ }
+ case Phone.TYPE_FAX_HOME: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_HOME, VCardConstants.PARAM_TYPE_FAX));
+ break;
+ }
+ case Phone.TYPE_FAX_WORK: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_FAX));
+ break;
+ }
+ case Phone.TYPE_MOBILE: {
+ parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ break;
+ }
+ case Phone.TYPE_PAGER: {
+ if (mIsDoCoMo) {
+ // Not sure about the reason, but previous implementation had
+ // used "VOICE" instead of "PAGER"
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+ }
+ break;
+ }
+ case Phone.TYPE_OTHER: {
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ break;
+ }
+ case Phone.TYPE_CAR: {
+ parameterList.add(VCardConstants.PARAM_TYPE_CAR);
+ break;
+ }
+ case Phone.TYPE_COMPANY_MAIN: {
+ // There's no relevant field in vCard (at least 2.1).
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ isPrimary = true;
+ break;
+ }
+ case Phone.TYPE_ISDN: {
+ parameterList.add(VCardConstants.PARAM_TYPE_ISDN);
+ break;
+ }
+ case Phone.TYPE_MAIN: {
+ isPrimary = true;
+ break;
+ }
+ case Phone.TYPE_OTHER_FAX: {
+ parameterList.add(VCardConstants.PARAM_TYPE_FAX);
+ break;
+ }
+ case Phone.TYPE_TELEX: {
+ parameterList.add(VCardConstants.PARAM_TYPE_TLX);
+ break;
+ }
+ case Phone.TYPE_WORK_MOBILE: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_CELL));
+ break;
+ }
+ case Phone.TYPE_WORK_PAGER: {
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ // See above.
+ if (mIsDoCoMo) {
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+ }
+ break;
+ }
+ case Phone.TYPE_MMS: {
+ parameterList.add(VCardConstants.PARAM_TYPE_MSG);
+ break;
+ }
+ case Phone.TYPE_CUSTOM: {
+ if (TextUtils.isEmpty(label)) {
+ // Just ignore the custom type.
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else if (VCardUtils.isMobilePhoneLabel(label)) {
+ parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ } else {
+ final String upperLabel = label.toUpperCase();
+ if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) {
+ parameterList.add(upperLabel);
+ } else if (VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
+ // "TYPE=" string.
+ parameterList.add("X-" + label);
+ }
+ }
+ break;
+ }
+ case Phone.TYPE_RADIO:
+ case Phone.TYPE_TTY_TDD:
+ default: {
+ break;
+ }
+ }
+
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+
+ if (parameterList.isEmpty()) {
+ appendUncommonPhoneType(mBuilder, type);
+ } else {
+ appendTypeParameters(parameterList);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
/**
- * @param group
+ * Appends phone type string which may not be available in some devices.
*/
- void propertyGroup(String group);
-
+ private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
+ if (mIsDoCoMo) {
+ // The previous implementation for DoCoMo had been conservative
+ // about miscellaneous types.
+ builder.append(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ String phoneType = VCardUtils.getPhoneTypeString(type);
+ if (phoneType != null) {
+ appendTypeParameter(phoneType);
+ } else {
+ Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
+ }
+ }
+ }
+
/**
- * @param name
- * N <br>
- * N
+ * @param encodedValue Must be encoded by BASE64
+ * @param photoType
*/
- void propertyName(String name);
+ public void appendPhotoLine(final String encodedValue, final String photoType) {
+ StringBuilder tmpBuilder = new StringBuilder();
+ tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ if (mIsV30) {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
+ } else {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
+ }
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameter(tmpBuilder, photoType);
+ tmpBuilder.append(VCARD_DATA_SEPARATOR);
+ tmpBuilder.append(encodedValue);
+
+ final String tmpStr = tmpBuilder.toString();
+ tmpBuilder = new StringBuilder();
+ int lineCount = 0;
+ final int length = tmpStr.length();
+ final int maxNumForFirstLine = VCardConstants.MAX_CHARACTER_NUMS_BASE64_V30
+ - VCARD_END_OF_LINE.length();
+ final int maxNumInGeneral = maxNumForFirstLine - VCARD_WS.length();
+ int maxNum = maxNumForFirstLine;
+ for (int i = 0; i < length; i++) {
+ tmpBuilder.append(tmpStr.charAt(i));
+ lineCount++;
+ if (lineCount > maxNum) {
+ tmpBuilder.append(VCARD_END_OF_LINE);
+ tmpBuilder.append(VCARD_WS);
+ maxNum = maxNumInGeneral;
+ lineCount = 0;
+ }
+ }
+ mBuilder.append(tmpBuilder.toString());
+ mBuilder.append(VCARD_END_OF_LINE);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
+ if (!sAllowedAndroidPropertySet.contains(mimeType)) {
+ return;
+ }
+ final List<String> rawValueList = new ArrayList<String>();
+ for (int i = 1; i <= VCardConstants.MAX_DATA_COLUMN; i++) {
+ String value = contentValues.getAsString("data" + i);
+ if (value == null) {
+ value = "";
+ }
+ rawValueList.add(value);
+ }
+
+ boolean needCharset =
+ (mShouldAppendCharsetParam &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ mBuilder.append(VCardConstants.PROPERTY_X_ANDROID_CUSTOM);
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(mimeType); // Should not be encoded.
+ for (String rawValue : rawValueList) {
+ final String encodedValue;
+ if (reallyUseQuotedPrintable) {
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard 3.0
+ // (which says "When generating a content line, lines longer than
+ // 75 characters SHOULD be folded"), though several
+ // (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedValue);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final String rawValue) {
+ appendLineWithCharsetAndQPDetection(propertyName, null, rawValue);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(
+ final String propertyName, final List<String> rawValueList) {
+ appendLineWithCharsetAndQPDetection(propertyName, null, rawValueList);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final List<String> parameterList, final String rawValue) {
+ final boolean needCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawValue);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue));
+ appendLine(propertyName, parameterList,
+ rawValue, needCharset, reallyUseQuotedPrintable);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final List<String> parameterList, final List<String> rawValueList) {
+ boolean needCharset =
+ (mShouldAppendCharsetParam &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ appendLine(propertyName, parameterList, rawValueList,
+ needCharset, reallyUseQuotedPrintable);
+ }
+
+ /**
+ * Appends one line with a given property name and value.
+ */
+ public void appendLine(final String propertyName, final String rawValue) {
+ appendLine(propertyName, rawValue, false, false);
+ }
+
+ public void appendLine(final String propertyName, final List<String> rawValueList) {
+ appendLine(propertyName, rawValueList, false, false);
+ }
+
+ public void appendLine(final String propertyName,
+ final String rawValue, final boolean needCharset,
+ boolean reallyUseQuotedPrintable) {
+ appendLine(propertyName, null, rawValue, needCharset, reallyUseQuotedPrintable);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final String rawValue) {
+ appendLine(propertyName, parameterList, rawValue, false, false);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final String rawValue, final boolean needCharset,
+ boolean reallyUseQuotedPrintable) {
+ mBuilder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+
+ final String encodedValue;
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ 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.
+ encodedValue = escapeCharacters(rawValue);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendLine(final String propertyName, final List<String> rawValueList,
+ final boolean needCharset, boolean needQuotedPrintable) {
+ appendLine(propertyName, null, rawValueList, needCharset, needQuotedPrintable);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final List<String> rawValueList, final boolean needCharset,
+ final boolean needQuotedPrintable) {
+ mBuilder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (needQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ boolean first = true;
+ for (String rawValue : rawValueList) {
+ final String encodedValue;
+ if (needQuotedPrintable) {
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard 3.0
+ // (which says "When generating a content line, lines longer than
+ // 75 characters SHOULD be folded"), though several
+ // (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ }
+ mBuilder.append(encodedValue);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ /**
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+ */
+ private void appendTypeParameters(final List<String> types) {
+ // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
+ // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
+ boolean first = true;
+ for (final String typeValue : types) {
+ // Note: vCard 3.0 specifies the different type of acceptable type Strings, but
+ // we don't emit that kind of vCard 3.0 specific type since there should be
+ // high probabilyty in which external importers cannot understand them.
+ //
+ // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they
+ // are quoted.)
+ if (!VCardUtils.isV21Word(typeValue)) {
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ }
+ appendTypeParameter(typeValue);
+ }
+ }
/**
- * @param type
- * LANGUAGE \ ENCODING <br>
- * ;LANGUage= \ ;ENCODING=
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
*/
- void propertyParamType(String type);
+ private void appendTypeParameter(final String type) {
+ appendTypeParameter(mBuilder, type);
+ }
+
+ private void appendTypeParameter(final StringBuilder builder, final String type) {
+ // Refrain from using appendType() so that "TYPE=" is not be appended when the
+ // device is DoCoMo's (just for safety).
+ //
+ // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
+ if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
+ builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
+ }
+ builder.append(type);
+ }
/**
- * @param value
- * FR-EN \ GBK <br>
- * FR-EN \ GBK
+ * Returns true when the property line should contain charset parameter
+ * information. This method may return true even when vCard version is 3.0.
+ *
+ * Strictly, adding charset information is invalid in VCard 3.0.
+ * However we'll add the info only when charset we use is not UTF-8
+ * in vCard 3.0 format, since parser side may be able to use the charset
+ * via this field, though we may encounter another problem by adding it.
+ *
+ * e.g. Japanese mobile phones use Shift_Jis while RFC 2426
+ * recommends UTF-8. By adding this field, parsers may be able
+ * to know this text is NOT UTF-8 but Shift_Jis.
*/
- void propertyParamValue(String value);
+ private boolean shouldAppendCharsetParam(String...propertyValueList) {
+ if (!mShouldAppendCharsetParam) {
+ return false;
+ }
+ for (String propertyValue : propertyValueList) {
+ if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String encodeQuotedPrintable(final String str) {
+ if (TextUtils.isEmpty(str)) {
+ return "";
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ int index = 0;
+ int lineCount = 0;
+ byte[] strArray = null;
+
+ try {
+ strArray = str.getBytes(mCharsetString);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+ + "Try default charset");
+ strArray = str.getBytes();
+ }
+ while (index < strArray.length) {
+ builder.append(String.format("=%02X", strArray[index]));
+ index += 1;
+ lineCount += 3;
+
+ if (lineCount >= 67) {
+ // Specification requires CRLF must be inserted before the
+ // length of the line
+ // becomes more than 76.
+ // Assuming that the next character is a multi-byte character,
+ // it will become
+ // 6 bytes.
+ // 76 - 6 - 3 = 67
+ builder.append("=\r\n");
+ lineCount = 0;
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Append '\' to the characters which should be escaped. The character set is different
+ * not only between vCard 2.1 and vCard 3.0 but also among each device.
+ *
+ * Note that Quoted-Printable string must not be input here.
+ */
+ @SuppressWarnings("fallthrough")
+ private String escapeCharacters(final String unescaped) {
+ if (TextUtils.isEmpty(unescaped)) {
+ return "";
+ }
+
+ final StringBuilder tmpBuilder = new StringBuilder();
+ final int length = unescaped.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = unescaped.charAt(i);
+ switch (ch) {
+ case ';': {
+ tmpBuilder.append('\\');
+ tmpBuilder.append(';');
+ break;
+ }
+ case '\r': {
+ if (i + 1 < length) {
+ char nextChar = unescaped.charAt(i);
+ if (nextChar == '\n') {
+ break;
+ } else {
+ // fall through
+ }
+ } else {
+ // fall through
+ }
+ }
+ case '\n': {
+ // In vCard 2.1, there's no specification about this, while
+ // vCard 3.0 explicitly requires this should be encoded to "\n".
+ tmpBuilder.append("\\n");
+ break;
+ }
+ case '\\': {
+ if (mIsV30) {
+ tmpBuilder.append("\\\\");
+ break;
+ } else {
+ // fall through
+ }
+ }
+ case '<':
+ case '>': {
+ if (mIsDoCoMo) {
+ tmpBuilder.append('\\');
+ tmpBuilder.append(ch);
+ } else {
+ tmpBuilder.append(ch);
+ }
+ break;
+ }
+ case ',': {
+ if (mIsV30) {
+ tmpBuilder.append("\\,");
+ } else {
+ tmpBuilder.append(ch);
+ }
+ break;
+ }
+ default: {
+ tmpBuilder.append(ch);
+ break;
+ }
+ }
+ }
+ return tmpBuilder.toString();
+ }
- void propertyValues(List<String> values);
+ @Override
+ public String toString() {
+ if (!mEndAppended) {
+ if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+ appendLine(VCardConstants.PROPERTY_X_REDUCTION, "");
+ appendLine(VCardConstants.PROPERTY_X_NO, "");
+ appendLine(VCardConstants.PROPERTY_X_DCM_HMN_MODE, "");
+ }
+ appendLine(VCardConstants.PROPERTY_END, VCARD_DATA_VCARD);
+ mEndAppended = true;
+ }
+ return mBuilder.toString();
+ }
}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 7807595..389c9f4 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -38,11 +38,10 @@ import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
-import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.CharsetUtils;
@@ -55,13 +54,13 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
+import java.lang.reflect.Method;
+import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
* <p>
@@ -74,19 +73,37 @@ import java.util.Set;
* Usually, this class should be used like this.
* </p>
*
- * <pre class="prettyprint"> VCardComposer composer = null; try { composer = new
- * VCardComposer(context); composer.addHandler(composer.new
- * HandlerForOutputStream(outputStream)); if (!composer.init()) { // Do
- * something handling the situation. return; } while (!composer.isAfterLast()) {
- * if (mCanceled) { // Assume a user may cancel this operation during the
- * export. return; } if (!composer.createOneEntry()) { // Do something handling
- * the error situation. return; } } } finally { if (composer != null) {
- * composer.terminate(); } } </pre>
+ * <pre class="prettyprint">VCardComposer composer = null;
+ * try {
+ * composer = new VCardComposer(context);
+ * composer.addHandler(
+ * composer.new HandlerForOutputStream(outputStream));
+ * if (!composer.init()) {
+ * // Do something handling the situation.
+ * return;
+ * }
+ * while (!composer.isAfterLast()) {
+ * if (mCanceled) {
+ * // Assume a user may cancel this operation during the export.
+ * return;
+ * }
+ * if (!composer.createOneEntry()) {
+ * // Do something handling the error situation.
+ * return;
+ * }
+ * }
+ * } finally {
+ * if (composer != null) {
+ * composer.terminate();
+ * }
+ * } </pre>
*/
public class VCardComposer {
- private static final String LOG_TAG = "vcard.VCardComposer";
+ private static final String LOG_TAG = "VCardComposer";
- private static final String DEFAULT_EMAIL_TYPE = Constants.ATTR_TYPE_INTERNET;
+ 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";
@@ -97,31 +114,57 @@ public class VCardComposer {
public static final String FAILURE_REASON_NOT_INITIALIZED =
"The vCard composer object is not correctly initialized";
+ /** Should be visible only from developers... (no need to translate, hopefully) */
+ public static final String FAILURE_REASON_UNSUPPORTED_URI =
+ "The Uri vCard composer received is not supported by the composer.";
+
public static final String NO_ERROR = "No error";
- private static final Uri sDataRequestUri;
+ public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+
+ // Property for call log entry
+ private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
+ private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
+ private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
+ private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
+
+ private static final String SHIFT_JIS = "SHIFT_JIS";
+ private static final String UTF_8 = "UTF-8";
+
+ /**
+ * Special URI for testing.
+ */
+ public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard";
+ public static final Uri VCARD_TEST_AUTHORITY_URI =
+ Uri.parse("content://" + VCARD_TEST_AUTHORITY);
+ public static final Uri CONTACTS_TEST_CONTENT_URI =
+ Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts");
+
+ private static final Map<Integer, String> sImMap;
static {
- Uri.Builder builder = RawContacts.CONTENT_URI.buildUpon();
- builder.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1");
- sDataRequestUri = builder.build();
+ sImMap = new HashMap<Integer, String>();
+ sImMap.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+ sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+ sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+ 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.
}
public static interface OneEntryHandler {
public boolean onInit(Context context);
-
public boolean onEntryCreated(String vcard);
-
public void onTerminate();
}
/**
* <p>
- * An useful example handler, which emits VCard String to outputstream one
- * by one.
+ * An useful example handler, which emits VCard String to outputstream one by one.
* </p>
* <p>
- * The input OutputStream object is closed() on {{@link #onTerminate()}.
+ * The input OutputStream object is closed() on {@link #onTerminate()}.
* Must not close the stream outside.
* </p>
*/
@@ -155,7 +198,7 @@ public class VCardComposer {
if (mIsDoCoMo) {
try {
// Create one empty entry.
- mWriter.write(createOneEntryInternal("-1"));
+ mWriter.write(createOneEntryInternal("-1", null));
} catch (IOException e) {
Log.e(LOG_TAG,
"IOException occurred during exportOneContactData: "
@@ -213,109 +256,23 @@ public class VCardComposer {
}
}
- public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
- private static final String VCARD_PROPERTY_ADR = "ADR";
- private static final String VCARD_PROPERTY_BEGIN = "BEGIN";
- private static final String VCARD_PROPERTY_EMAIL = "EMAIL";
- private static final String VCARD_PROPERTY_END = "END";
- private static final String VCARD_PROPERTY_NAME = "N";
- private static final String VCARD_PROPERTY_FULL_NAME = "FN";
- private static final String VCARD_PROPERTY_NOTE = "NOTE";
- private static final String VCARD_PROPERTY_ORG = "ORG";
- private static final String VCARD_PROPERTY_SOUND = "SOUND";
- private static final String VCARD_PROPERTY_SORT_STRING = "SORT-STRING";
- private static final String VCARD_PROPERTY_NICKNAME = "NICKNAME";
- private static final String VCARD_PROPERTY_TEL = "TEL";
- private static final String VCARD_PROPERTY_TITLE = "TITLE";
- private static final String VCARD_PROPERTY_PHOTO = "PHOTO";
- private static final String VCARD_PROPERTY_VERSION = "VERSION";
- private static final String VCARD_PROPERTY_URL = "URL";
- private static final String VCARD_PROPERTY_BIRTHDAY = "BDAY";
-
- private static final String VCARD_PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
- private static final String VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
- private static final String VCARD_PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
-
- // Android specific properties
- // TODO: ues extra MIME-TYPE instead of adding this kind of inflexible fields
- private static final String VCARD_PROPERTY_X_NICKNAME = "X-NICKNAME";
-
- // Property for call log entry
- private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
- private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
- private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
- private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
-
- // Properties for DoCoMo vCard.
- private static final String VCARD_PROPERTY_X_CLASS = "X-CLASS";
- private static final String VCARD_PROPERTY_X_REDUCTION = "X-REDUCTION";
- private static final String VCARD_PROPERTY_X_NO = "X-NO";
- private static final String VCARD_PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
- private static final String VCARD_DATA_VCARD = "VCARD";
- private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
- private static final String VCARD_ATTR_SEPARATOR = ";";
- private static final String VCARD_COL_SEPARATOR = "\r\n";
- private static final String VCARD_DATA_SEPARATOR = ":";
- private static final String VCARD_ITEM_SEPARATOR = ";";
- private static final String VCARD_WS = " ";
- private static final String VCARD_ATTR_EQUAL = "=";
-
- // Type strings are now in VCardConstants.java.
-
- private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
- private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
-
- private static final String SHIFT_JIS = "SHIFT_JIS";
-
private final Context mContext;
private final int mVCardType;
private final boolean mCareHandlerErrors;
private final ContentResolver mContentResolver;
- // Convenient member variables about the restriction of the vCard format.
- // Used for not calling the same methods returning same results.
- private final boolean mIsV30;
- private final boolean mIsJapaneseMobilePhone;
- private final boolean mOnlyOneNoteFieldIsAvailable;
private final boolean mIsDoCoMo;
- private final boolean mUsesQuotedPrintable;
- private final boolean mUsesAndroidProperty;
- private final boolean mUsesDefactProperty;
- private final boolean mUsesUtf8;
private final boolean mUsesShiftJis;
- private final boolean mUsesQPToPrimaryProperties;
-
private Cursor mCursor;
private int mIdColumn;
private final String mCharsetString;
- private final String mVCardAttributeCharset;
private boolean mTerminateIsCalled;
final private List<OneEntryHandler> mHandlerList;
private String mErrorReason = NO_ERROR;
- private static final Map<Integer, String> sImMap;
-
- static {
- sImMap = new HashMap<Integer, String>();
- sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
- sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
- sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
- sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
- sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
- sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
- // Google talk is a special case.
- }
-
- private boolean mIsCallLogComposer = false;
-
- private boolean mNeedPhotoForVCard = true;
+ private boolean mIsCallLogComposer;
private static final String[] sContactsProjection = new String[] {
Contacts._ID,
@@ -336,110 +293,83 @@ public class VCardComposer {
private static final String FLAG_TIMEZONE_UTC = "Z";
public VCardComposer(Context context) {
- this(context, VCardConfig.VCARD_TYPE_DEFAULT, true, false, true);
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
}
- public VCardComposer(Context context, String vcardTypeStr,
- boolean careHandlerErrors) {
- this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr),
- careHandlerErrors, false, true);
+ public VCardComposer(Context context, int vcardType) {
+ this(context, vcardType, true);
}
- public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
- this(context, vcardType, careHandlerErrors, false, true);
+ public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
+ this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
}
/**
* Construct for supporting call log entry vCard composing.
- *
- * @param isCallLogComposer true if this composer is for creating Call Log vCard.
*/
- public VCardComposer(Context context, int vcardType, boolean careHandlerErrors,
- boolean isCallLogComposer, boolean needPhotoInVCard) {
+ public VCardComposer(final Context context, final int vcardType,
+ final boolean careHandlerErrors) {
mContext = context;
mVCardType = vcardType;
mCareHandlerErrors = careHandlerErrors;
- mIsCallLogComposer = isCallLogComposer;
- mNeedPhotoForVCard = needPhotoInVCard;
mContentResolver = context.getContentResolver();
- mIsV30 = VCardConfig.isV30(vcardType);
- mUsesQuotedPrintable = VCardConfig.usesQuotedPrintable(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mIsJapaneseMobilePhone = VCardConfig
- .needsToConvertPhoneticString(vcardType);
- mOnlyOneNoteFieldIsAvailable = VCardConfig
- .onlyOneNoteFieldIsAvailable(vcardType);
- mUsesAndroidProperty = VCardConfig
- .usesAndroidSpecificProperty(vcardType);
- mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
- mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
- mUsesQPToPrimaryProperties = VCardConfig.usesQPToPrimaryProperties(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
if (mIsDoCoMo) {
- mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- // 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).
- mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+ 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;
+ }
+ mCharsetString = charset;
} else if (mUsesShiftJis) {
- mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+ 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";
- mVCardAttributeCharset = "CHARSET=UTF-8";
+ mCharsetString = UTF_8;
}
}
/**
- * This static function is to compose vCard for phone own number
+ * Must be called before {@link #init()}.
*/
- public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
- String phoneNumber, boolean vcardVer21) {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (!vcardVer21) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
-
- boolean needCharset = false;
- if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
- needCharset = true;
- }
- // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, phoneName, needCharset, false);
- appendVCardLine(builder, VCARD_PROPERTY_NAME, phoneName, needCharset, false);
-
- if (!TextUtils.isEmpty(phoneNumber)) {
- String label = Integer.toString(phonetype);
- appendVCardTelephoneLine(builder, phonetype, label, phoneNumber);
+ public void addHandler(OneEntryHandler handler) {
+ if (handler != null) {
+ mHandlerList.add(handler);
}
-
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
- return builder.toString();
}
/**
- * Must call before {{@link #init()}.
+ * @return Returns true when initialization is successful and all the other
+ * methods are available. Returns false otherwise.
*/
- public void addHandler(OneEntryHandler handler) {
- mHandlerList.add(handler);
- }
-
public boolean init() {
return init(null, null);
}
+ public boolean init(final String selection, final String[] selectionArgs) {
+ return init(Contacts.CONTENT_URI, selection, selectionArgs, null);
+ }
+
/**
- * @return Returns true when initialization is successful and all the other
- * methods are available. Returns false otherwise.
+ * Note that this is unstable interface, may be deleted in the future.
*/
- public boolean init(final String selection, final String[] selectionArgs) {
+ public boolean init(final Uri contentUri, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ if (contentUri == null) {
+ return false;
+ }
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -458,13 +388,19 @@ public class VCardComposer {
}
}
- if (mIsCallLogComposer) {
- mCursor = mContentResolver.query(CallLog.Calls.CONTENT_URI, sCallLogProjection,
- selection, selectionArgs, null);
+ final String[] projection;
+ if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
+ projection = sCallLogProjection;
+ mIsCallLogComposer = true;
+ } else if (Contacts.CONTENT_URI.equals(contentUri) ||
+ CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
+ projection = sContactsProjection;
} else {
- mCursor = mContentResolver.query(Contacts.CONTENT_URI, sContactsProjection,
- selection, selectionArgs, null);
+ mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
+ return false;
}
+ mCursor = mContentResolver.query(
+ contentUri, projection, selection, selectionArgs, sortOrder);
if (mCursor == null) {
mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
@@ -493,6 +429,14 @@ public class VCardComposer {
}
public boolean createOneEntry() {
+ return createOneEntry(null);
+ }
+
+ /**
+ * @param getEntityIteratorMethod For Dependency Injection.
+ * @hide just for testing.
+ */
+ public boolean createOneEntry(Method getEntityIteratorMethod) {
if (mCursor == null || mCursor.isAfterLast()) {
mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false;
@@ -504,7 +448,8 @@ public class VCardComposer {
vcard = createOneCallLogEntryInternal();
} else {
if (mIdColumn >= 0) {
- vcard = createOneEntryInternal(mCursor.getString(mIdColumn));
+ vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
+ getEntityIteratorMethod);
} else {
Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn);
return true;
@@ -513,8 +458,7 @@ public class VCardComposer {
} catch (OutOfMemoryError error) {
// Maybe some data (e.g. photo) is too big to have in memory. But it
// should be rare.
- Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: "
- + name);
+ Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: " + name);
System.gc();
// TODO: should tell users what happened?
return true;
@@ -541,107 +485,48 @@ public class VCardComposer {
return true;
}
- /**
- * Format according to RFC 2445 DATETIME type.
- * The format is: ("%Y%m%dT%H%M%SZ").
- */
- private final String toRfc2455Format(final long millSecs) {
- Time startDate = new Time();
- startDate.set(millSecs);
- String date = startDate.format2445();
- return date + FLAG_TIMEZONE_UTC;
- }
-
- /**
- * Try to append the property line for a call history time stamp field if possible.
- * Do nothing if the call log type gotton from the database is invalid.
- */
- private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
- // Extension for call history as defined in
- // in the Specification for Ic Mobile Communcation - ver 1.1,
- // Oct 2000. This is used to send the details of the call
- // history - missed, incoming, outgoing along with date and time
- // to the requesting device (For example, transferring phone book
- // when connected over bluetooth)
- //
- // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
- final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
- final String callLogTypeStr;
- switch (callLogType) {
- case Calls.INCOMING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
- break;
- }
- case Calls.OUTGOING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
- break;
- }
- case Calls.MISSED_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
- break;
- }
- default: {
- Log.w(LOG_TAG, "Call log type not correct.");
- return;
- }
- }
-
- final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
- builder.append(VCARD_PROPERTY_X_TIMESTAMP);
- builder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(builder, callLogTypeStr);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(toRfc2455Format(dateAsLong));
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private String createOneCallLogEntryInternal() {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
- String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
- if (TextUtils.isEmpty(name)) {
- name = mCursor.getString(NUMBER_COLUMN_INDEX);
- }
- final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
- // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, name, needCharset, false);
- appendVCardLine(builder, VCARD_PROPERTY_NAME, name, needCharset, false);
-
- String number = mCursor.getString(NUMBER_COLUMN_INDEX);
- int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
- String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
- if (TextUtils.isEmpty(label)) {
- label = Integer.toString(type);
- }
- appendVCardTelephoneLine(builder, type, label, number);
- tryAppendCallHistoryTimeStampField(builder);
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
- return builder.toString();
- }
-
- private String createOneEntryInternal(final String contactId) {
+ private String createOneEntryInternal(final String contactId,
+ Method getEntityIteratorMethod) {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
- final String selection = Data.CONTACT_ID + "=?";
- final String[] selectionArgs = new String[] {contactId};
// The resolver may return the entity iterator with no data. It is possiible.
// e.g. If all the data in the contact of the given contact id are not exportable ones,
// they are hidden from the view of this method, though contact id itself exists.
boolean dataExists = false;
EntityIterator entityIterator = null;
try {
- entityIterator = mContentResolver.queryEntities(
- sDataRequestUri, selection, selectionArgs, null);
+
+ if (getEntityIteratorMethod != null) {
+ try {
+ final Uri uri = RawContacts.CONTENT_URI.buildUpon()
+ .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
+ .build();
+ final String selection = Data.CONTACT_ID + "=?";
+ final String[] selectionArgs = new String[] {contactId};
+ entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
+ mContentResolver, uri, selection, selectionArgs, null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ final Uri uri = RawContacts.CONTENT_URI.buildUpon()
+ .appendEncodedPath(contactId)
+ .appendEncodedPath(RawContacts.Entity.CONTENT_DIRECTORY)
+ .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
+ .build();
+ entityIterator = RawContacts.newEntityIterator(mContentResolver.query(
+ uri, null, null, null, null));
+ }
+
+ if (entityIterator == null) {
+ Log.e(LOG_TAG, "EntityIterator is null");
+ return "";
+ }
+
dataExists = entityIterator.hasNext();
while (entityIterator.hasNext()) {
Entity entity = entityIterator.next();
- for (NamedContentValues namedContentValues : entity
- .getSubValues()) {
+ for (NamedContentValues namedContentValues : entity.getSubValues()) {
ContentValues contentValues = namedContentValues.values;
String key = contentValues.getAsString(Data.MIMETYPE);
if (key != null) {
@@ -669,38 +554,19 @@ public class VCardComposer {
return "";
}
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
-
- appendStructuredNames(builder, contentValuesListMap);
- appendNickNames(builder, contentValuesListMap);
- appendPhones(builder, contentValuesListMap);
- appendEmails(builder, contentValuesListMap);
- appendPostals(builder, contentValuesListMap);
- appendIms(builder, contentValuesListMap);
- appendWebsites(builder, contentValuesListMap);
- appendBirthday(builder, contentValuesListMap);
- appendOrganizations(builder, contentValuesListMap);
- if (mNeedPhotoForVCard) {
- appendPhotos(builder, contentValuesListMap);
- }
- appendNotes(builder, contentValuesListMap);
- // TODO: GroupMembership
-
- if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
- appendVCardLine(builder, VCARD_PROPERTY_X_REDUCTION, "");
- appendVCardLine(builder, VCARD_PROPERTY_X_NO, "");
- appendVCardLine(builder, VCARD_PROPERTY_X_DCM_HMN_MODE, "");
- }
-
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
+ 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();
}
@@ -713,8 +579,7 @@ public class VCardComposer {
try {
mCursor.close();
} catch (SQLiteException e) {
- Log.e(LOG_TAG, "SQLiteException on Cursor#close(): "
- + e.getMessage());
+ Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
}
mCursor = null;
}
@@ -750,1302 +615,98 @@ public class VCardComposer {
return mErrorReason;
}
- private void appendStructuredNames(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(StructuredName.CONTENT_ITEM_TYPE);
- if (contentValuesList != null && contentValuesList.size() > 0) {
- appendStructuredNamesInternal(builder, contentValuesList);
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- } else if (mIsV30) {
- // vCard 3.0 requires "N" and "FN" properties.
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
- }
- }
-
- private boolean containsNonEmptyName(ContentValues contentValues) {
- final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
- final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
- final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
- final String prefix = contentValues.getAsString(StructuredName.PREFIX);
- final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
- final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
- return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
- TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
- TextUtils.isEmpty(suffix) && TextUtils.isEmpty(displayName));
- }
-
- private void appendStructuredNamesInternal(final StringBuilder builder,
- final List<ContentValues> contentValuesList) {
- // For safety, we'll emit just one value around StructuredName, as external importers
- // may get confused with multiple "N", "FN", etc. properties, though it is valid in
- // vCard spec.
- ContentValues primaryContentValues = null;
- ContentValues subprimaryContentValues = null;
- for (ContentValues contentValues : contentValuesList) {
- if (contentValues == null){
- continue;
- }
- Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
- if (isSuperPrimary != null && isSuperPrimary > 0) {
- // We choose "super primary" ContentValues.
- primaryContentValues = contentValues;
- break;
- } else if (primaryContentValues == null) {
- // We choose the first "primary" ContentValues
- // if "super primary" ContentValues does not exist.
- Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
- if (isPrimary != null && isPrimary > 0 &&
- containsNonEmptyName(contentValues)) {
- primaryContentValues = contentValues;
- // Do not break, since there may be ContentValues with "super primary"
- // afterword.
- } else if (subprimaryContentValues == null &&
- containsNonEmptyName(contentValues)) {
- subprimaryContentValues = contentValues;
- }
- }
- }
-
- if (primaryContentValues == null) {
- if (subprimaryContentValues != null) {
- // We choose the first ContentValues if any "primary" ContentValues does not exist.
- primaryContentValues = subprimaryContentValues;
- } else {
- Log.e(LOG_TAG, "All ContentValues given from database is empty.");
- primaryContentValues = new ContentValues();
- }
- }
-
- final String familyName = primaryContentValues
- .getAsString(StructuredName.FAMILY_NAME);
- final String middleName = primaryContentValues
- .getAsString(StructuredName.MIDDLE_NAME);
- final String givenName = primaryContentValues
- .getAsString(StructuredName.GIVEN_NAME);
- final String prefix = primaryContentValues
- .getAsString(StructuredName.PREFIX);
- final String suffix = primaryContentValues
- .getAsString(StructuredName.SUFFIX);
- final String displayName = primaryContentValues
- .getAsString(StructuredName.DISPLAY_NAME);
-
- if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
- final String encodedFamily;
- final String encodedGiven;
- final String encodedMiddle;
- final String encodedPrefix;
- final String encodedSuffix;
-
- final boolean reallyUseQuotedPrintableToName =
- (mUsesQPToPrimaryProperties &&
- !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
-
- if (reallyUseQuotedPrintableToName) {
- encodedFamily = encodeQuotedPrintable(familyName);
- encodedGiven = encodeQuotedPrintable(givenName);
- encodedMiddle = encodeQuotedPrintable(middleName);
- encodedPrefix = encodeQuotedPrintable(prefix);
- encodedSuffix = encodeQuotedPrintable(suffix);
- } else {
- encodedFamily = escapeCharacters(familyName);
- encodedGiven = escapeCharacters(givenName);
- encodedMiddle = escapeCharacters(middleName);
- encodedPrefix = escapeCharacters(prefix);
- encodedSuffix = escapeCharacters(suffix);
- }
-
- // N property. This order is specified by vCard spec and does not depend on countries.
- builder.append(VCARD_PROPERTY_NAME);
- if (shouldAppendCharsetAttribute(Arrays.asList(
- familyName, givenName, middleName, prefix, suffix))) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintableToName) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFamily);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedGiven);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedMiddle);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedPrefix);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedSuffix);
- builder.append(VCARD_COL_SEPARATOR);
-
- final String fullname = VCardUtils.constructNameFromElements(
- VCardConfig.getNameOrderType(mVCardType),
- encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix);
- final boolean reallyUseQuotedPrintableToFullname =
- mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(fullname);
-
- final String encodedFullname =
- reallyUseQuotedPrintableToFullname ?
- encodeQuotedPrintable(fullname) :
- escapeCharacters(fullname);
-
- // FN property
- builder.append(VCARD_PROPERTY_FULL_NAME);
- if (shouldAppendCharsetAttribute(encodedFullname)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintableToFullname) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFullname);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (!TextUtils.isEmpty(displayName)) {
- final boolean reallyUseQuotedPrintableToDisplayName =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
- final String encodedDisplayName =
- reallyUseQuotedPrintableToDisplayName ?
- encodeQuotedPrintable(displayName) :
- escapeCharacters(displayName);
-
- builder.append(VCARD_PROPERTY_NAME);
- if (shouldAppendCharsetAttribute(encodedDisplayName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintableToDisplayName) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedDisplayName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- } else if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
- }
-
- String phoneticFamilyName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_FAMILY_NAME);
- String phoneticMiddleName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
- String phoneticGivenName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_GIVEN_NAME);
- if (!(TextUtils.isEmpty(phoneticFamilyName)
- && TextUtils.isEmpty(phoneticMiddleName) &&
- TextUtils.isEmpty(phoneticGivenName))) { // if not empty
- if (mIsJapaneseMobilePhone) {
- phoneticFamilyName = VCardUtils
- .toHalfWidthString(phoneticFamilyName);
- phoneticMiddleName = VCardUtils
- .toHalfWidthString(phoneticMiddleName);
- phoneticGivenName = VCardUtils
- .toHalfWidthString(phoneticGivenName);
- }
-
- if (mIsV30) {
- final String sortString = VCardUtils
- .constructNameFromElements(mVCardType,
- phoneticFamilyName,
- phoneticMiddleName,
- phoneticGivenName);
- builder.append(VCARD_PROPERTY_SORT_STRING);
-
- // Do not need to care about QP, since vCard 3.0 does not allow it.
- final String encodedSortString = escapeCharacters(sortString);
- if (shouldAppendCharsetAttribute(encodedSortString)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedSortString);
- builder.append(VCARD_COL_SEPARATOR);
- } else {
- // Note: There is no appropriate property for expressing
- // phonetic name in vCard 2.1, while there is in
- // vCard 3.0 (SORT-STRING).
- // We chose to use DoCoMo's way 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.
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_X_IRMC_N);
-
- boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticFamilyName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticMiddleName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticGivenName)));
-
- final String encodedPhoneticFamilyName;
- final String encodedPhoneticMiddleName;
- final String encodedPhoneticGivenName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
- encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
- encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
- } else {
- encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
- encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
- encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
- }
-
- if (shouldAppendCharsetAttribute(Arrays.asList(
- encodedPhoneticFamilyName, encodedPhoneticMiddleName,
- encodedPhoneticGivenName))) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticFamilyName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedPhoneticGivenName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedPhoneticMiddleName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
- } else if (mIsDoCoMo) {
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_X_IRMC_N);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- if (mUsesDefactProperty) {
- if (!TextUtils.isEmpty(phoneticGivenName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
- final String encodedPhoneticGivenName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
- } else {
- encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
- }
- builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticGivenName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticGivenName);
- builder.append(VCARD_COL_SEPARATOR);
- }
- if (!TextUtils.isEmpty(phoneticMiddleName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
- final String encodedPhoneticMiddleName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
- } else {
- encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
- }
- builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticMiddleName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticMiddleName);
- builder.append(VCARD_COL_SEPARATOR);
- }
- if (!TextUtils.isEmpty(phoneticFamilyName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
- final String encodedPhoneticFamilyName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
- } else {
- encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
- }
- builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticFamilyName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticFamilyName);
- builder.append(VCARD_COL_SEPARATOR);
- }
- }
- }
-
- private void appendNickNames(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Nickname.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- final String propertyNickname;
- if (mIsV30) {
- propertyNickname = VCARD_PROPERTY_NICKNAME;
- } else if (mUsesAndroidProperty) {
- propertyNickname = VCARD_PROPERTY_X_NICKNAME;
- } else {
- // There's no way to add this field.
- return;
- }
-
- for (ContentValues contentValues : contentValuesList) {
- final String nickname = contentValues.getAsString(Nickname.NAME);
- if (TextUtils.isEmpty(nickname)) {
- continue;
- }
-
- final String encodedNickname;
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(nickname));
- if (reallyUseQuotedPrintable) {
- encodedNickname = encodeQuotedPrintable(nickname);
- } else {
- encodedNickname = escapeCharacters(nickname);
- }
-
- builder.append(propertyNickname);
- if (shouldAppendCharsetAttribute(propertyNickname)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedNickname);
- builder.append(VCARD_COL_SEPARATOR);
- }
- }
- }
-
- private void appendPhones(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Phone.CONTENT_ITEM_TYPE);
- boolean phoneLineExists = false;
- if (contentValuesList != null) {
- Set<String> phoneSet = new HashSet<String>();
- for (ContentValues contentValues : contentValuesList) {
- final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
- final String label = contentValues.getAsString(Phone.LABEL);
- String phoneNumber = contentValues.getAsString(Phone.NUMBER);
- if (phoneNumber != null) {
- phoneNumber = phoneNumber.trim();
- }
- if (TextUtils.isEmpty(phoneNumber)) {
- continue;
- }
- int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
-
- phoneLineExists = true;
- if (type == Phone.TYPE_PAGER) {
- phoneLineExists = true;
- if (!phoneSet.contains(phoneNumber)) {
- phoneSet.add(phoneNumber);
- appendVCardTelephoneLine(builder, type, label, phoneNumber);
- }
- } 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);
- if (phoneNumberList.isEmpty()) {
- continue;
- }
- phoneLineExists = true;
- for (String actualPhoneNumber : phoneNumberList) {
- if (!phoneSet.contains(actualPhoneNumber)) {
- final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
- SpannableStringBuilder tmpBuilder =
- new SpannableStringBuilder(actualPhoneNumber);
- PhoneNumberUtils.formatNumber(tmpBuilder, format);
- final String formattedPhoneNumber = tmpBuilder.toString();
- phoneSet.add(actualPhoneNumber);
- appendVCardTelephoneLine(builder, type, label, formattedPhoneNumber);
- }
- }
- }
- }
- }
-
- if (!phoneLineExists && mIsDoCoMo) {
- appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "");
- }
- }
-
- private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
- 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);
- if (Character.isDigit(ch)) {
- builder.append(ch);
- } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
- phoneList.add(builder.toString());
- builder = new StringBuilder();
- }
- }
- if (builder.length() > 0) {
- phoneList.add(builder.toString());
- }
-
- return phoneList;
- }
-
- private void appendEmails(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Email.CONTENT_ITEM_TYPE);
- boolean emailAddressExists = false;
- if (contentValuesList != null) {
- Set<String> addressSet = new HashSet<String>();
- for (ContentValues contentValues : contentValuesList) {
- Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
- final int type = (typeAsObject != null ?
- typeAsObject : Email.TYPE_OTHER);
- final String label = contentValues.getAsString(Email.LABEL);
- String emailAddress = contentValues.getAsString(Email.DATA);
- if (emailAddress != null) {
- emailAddress = emailAddress.trim();
- }
- if (TextUtils.isEmpty(emailAddress)) {
- continue;
- }
- emailAddressExists = true;
- if (!addressSet.contains(emailAddress)) {
- addressSet.add(emailAddress);
- appendVCardEmailLine(builder, type, label, emailAddress);
- }
- }
- }
-
- if (!emailAddressExists && mIsDoCoMo) {
- appendVCardEmailLine(builder, Email.TYPE_HOME, "", "");
- }
- }
-
- private void appendPostals(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(StructuredPostal.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- if (mIsDoCoMo) {
- appendPostalsForDoCoMo(builder, contentValuesList);
- } else {
- appendPostalsForGeneric(builder, contentValuesList);
- }
- } else if (mIsDoCoMo) {
- builder.append(VCARD_PROPERTY_ADR);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_HOME);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
- }
-
/**
- * Tries to append just one line. If there's no appropriate address
- * information, append an empty line.
+ * This static function is to compose vCard for phone own number
*/
- private void appendPostalsForDoCoMo(final StringBuilder builder,
- final List<ContentValues> contentValuesList) {
- // TODO: from old, inefficient code. fix this.
- if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
- StructuredPostal.TYPE_HOME)) {
- return;
- }
- if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
- StructuredPostal.TYPE_WORK)) {
- return;
- }
- if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
- StructuredPostal.TYPE_OTHER)) {
- return;
- }
- if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
- StructuredPostal.TYPE_CUSTOM)) {
- return;
- }
-
- Log.w(LOG_TAG,
- "Should not come here. Must have at least one postal data.");
- }
-
- private boolean appendPostalsForDoCoMoInternal(final StringBuilder builder,
- final List<ContentValues> contentValuesList, Integer preferedType) {
- for (ContentValues contentValues : contentValuesList) {
- final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
- final String label = contentValues.getAsString(StructuredPostal.LABEL);
- if (type == preferedType) {
- appendVCardPostalLine(builder, type, label, contentValues);
- return true;
- }
- }
- return false;
- }
-
- private void appendPostalsForGeneric(final StringBuilder builder,
- final List<ContentValues> contentValuesList) {
- for (ContentValues contentValues : contentValuesList) {
- final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
- final String label = contentValues.getAsString(StructuredPostal.LABEL);
- if (type != null) {
- appendVCardPostalLine(builder, type, label, contentValues);
- }
- }
- }
-
- private void appendIms(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Im.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- Integer protocol = contentValues.getAsInteger(Im.PROTOCOL);
- String data = contentValues.getAsString(Im.DATA);
- if (data != null) {
- data = data.trim();
- }
- if (TextUtils.isEmpty(data)) {
- continue;
- }
-
- if (protocol != null && protocol == Im.PROTOCOL_GOOGLE_TALK) {
- if (VCardConfig.usesAndroidSpecificProperty(mVCardType)) {
- appendVCardLine(builder, Constants.PROPERTY_X_GOOGLE_TALK, data);
- }
- // TODO: add "X-GOOGLE TALK" case...
- }
- }
- }
- }
-
- private void appendWebsites(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Website.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- String website = contentValues.getAsString(Website.URL);
- if (website != null) {
- website = website.trim();
- }
- if (!TextUtils.isEmpty(website)) {
- appendVCardLine(builder, VCARD_PROPERTY_URL, website);
- }
- }
- }
- }
-
- private void appendBirthday(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Event.CONTENT_ITEM_TYPE);
- if (contentValuesList != null && contentValuesList.size() > 0) {
- Integer eventType = contentValuesList.get(0).getAsInteger(Event.TYPE);
- if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) {
- return;
- }
- // Theoretically, there must be only one birthday for each vCard data and
- // we are afraid of some parse error occuring in some devices, so
- // we emit only one birthday entry for now.
- String birthday = contentValuesList.get(0).getAsString(Event.START_DATE);
- if (birthday != null) {
- birthday = birthday.trim();
- }
- if (!TextUtils.isEmpty(birthday)) {
- appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
- }
- }
- }
-
- private void appendOrganizations(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Organization.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- String company = contentValues
- .getAsString(Organization.COMPANY);
- if (company != null) {
- company = company.trim();
- }
- String title = contentValues
- .getAsString(Organization.TITLE);
- if (title != null) {
- title = title.trim();
- }
-
- if (!TextUtils.isEmpty(company)) {
- appendVCardLine(builder, VCARD_PROPERTY_ORG, company,
- !VCardUtils.containsOnlyPrintableAscii(company),
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(company)));
- }
- if (!TextUtils.isEmpty(title)) {
- appendVCardLine(builder, VCARD_PROPERTY_TITLE, title,
- !VCardUtils.containsOnlyPrintableAscii(title),
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
- }
- }
+ public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
+ String phoneNumber, boolean vcardVer21) {
+ final int vcardType = (vcardVer21 ?
+ VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8 :
+ VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+ final VCardBuilder builder = new VCardBuilder(vcardType);
+ boolean needCharset = false;
+ if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
+ needCharset = true;
}
- }
+ builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false);
+ builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false);
- private void appendPhotos(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Photo.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
- if (data == null) {
- continue;
- }
- final String photoType;
- // Use some heuristics for guessing the format of the image.
- // TODO: there should be some general API for detecting the file format.
- if (data.length >= 3 && data[0] == 'G' && data[1] == 'I'
- && data[2] == 'F') {
- photoType = "GIF";
- } else if (data.length >= 4 && data[0] == (byte) 0x89
- && data[1] == 'P' && data[2] == 'N' && data[3] == 'G') {
- // Note: vCard 2.1 officially does not support PNG, but we
- // may have it
- // and using X- word like "X-PNG" may not let importers know
- // it is
- // PNG. So we use the String "PNG" as is...
- photoType = "PNG";
- } else if (data.length >= 2 && data[0] == (byte) 0xff
- && data[1] == (byte) 0xd8) {
- photoType = "JPEG";
- } else {
- Log.d(LOG_TAG, "Unknown photo type. Ignore.");
- continue;
- }
- final String photoString = VCardUtils.encodeBase64(data);
- if (photoString.length() > 0) {
- appendVCardPhotoLine(builder, photoString, photoType);
- }
- }
+ if (!TextUtils.isEmpty(phoneNumber)) {
+ String label = Integer.toString(phonetype);
+ builder.appendTelLine(phonetype, label, phoneNumber, false);
}
- }
- private void appendNotes(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList =
- contentValuesListMap.get(Note.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- if (mOnlyOneNoteFieldIsAvailable) {
- StringBuilder noteBuilder = new StringBuilder();
- boolean first = true;
- for (ContentValues contentValues : contentValuesList) {
- String note = contentValues.getAsString(Note.NOTE);
- if (note == null) {
- note = "";
- }
- if (note.length() > 0) {
- if (first) {
- first = false;
- } else {
- noteBuilder.append('\n');
- }
- noteBuilder.append(note);
- }
- }
- final String noteStr = noteBuilder.toString();
- // This means we scan noteStr completely twice, which is redundant.
- // But for now, we assume this is not so time-consuming..
- final boolean shouldAppendCharsetInfo =
- !VCardUtils.containsOnlyPrintableAscii(noteStr);
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
- shouldAppendCharsetInfo, reallyUseQuotedPrintable);
- } else {
- for (ContentValues contentValues : contentValuesList) {
- final String noteStr = contentValues.getAsString(Note.NOTE);
- if (!TextUtils.isEmpty(noteStr)) {
- final boolean shouldAppendCharsetInfo =
- !VCardUtils.containsOnlyPrintableAscii(noteStr);
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
- shouldAppendCharsetInfo, reallyUseQuotedPrintable);
- }
- }
- }
- }
+ return builder.toString();
}
/**
- * Append '\' to the characters which should be escaped. The character set is different
- * not only between vCard 2.1 and vCard 3.0 but also among each device.
- *
- * Note that Quoted-Printable string must not be input here.
+ * Format according to RFC 2445 DATETIME type.
+ * The format is: ("%Y%m%dT%H%M%SZ").
*/
- @SuppressWarnings("fallthrough")
- private String escapeCharacters(final String unescaped) {
- if (TextUtils.isEmpty(unescaped)) {
- return "";
- }
-
- final StringBuilder tmpBuilder = new StringBuilder();
- final int length = unescaped.length();
- for (int i = 0; i < length; i++) {
- char ch = unescaped.charAt(i);
- switch (ch) {
- case ';': {
- tmpBuilder.append('\\');
- tmpBuilder.append(';');
- break;
- }
- case '\r': {
- if (i + 1 < length) {
- char nextChar = unescaped.charAt(i);
- if (nextChar == '\n') {
- continue;
- } else {
- // fall through
- }
- } else {
- // fall through
- }
- }
- case '\n': {
- // In vCard 2.1, there's no specification about this, while
- // vCard 3.0 explicitly requires this should be encoded to "\n".
- tmpBuilder.append("\\n");
- break;
- }
- case '\\': {
- if (mIsV30) {
- tmpBuilder.append("\\\\");
- break;
- } else {
- // fall through
- }
- }
- case '<':
- case '>': {
- if (mIsDoCoMo) {
- tmpBuilder.append('\\');
- tmpBuilder.append(ch);
- } else {
- tmpBuilder.append(ch);
- }
- break;
- }
- case ',': {
- if (mIsV30) {
- tmpBuilder.append("\\,");
- } else {
- tmpBuilder.append(ch);
- }
- break;
- }
- default: {
- tmpBuilder.append(ch);
- break;
- }
- }
- }
- return tmpBuilder.toString();
- }
-
- private void appendVCardPhotoLine(final StringBuilder builder,
- final String encodedData, final String photoType) {
- StringBuilder tmpBuilder = new StringBuilder();
- tmpBuilder.append(VCARD_PROPERTY_PHOTO);
- tmpBuilder.append(VCARD_ATTR_SEPARATOR);
- if (mIsV30) {
- tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30);
- } else {
- tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V21);
- }
- tmpBuilder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(tmpBuilder, photoType);
- tmpBuilder.append(VCARD_DATA_SEPARATOR);
- tmpBuilder.append(encodedData);
-
- final String tmpStr = tmpBuilder.toString();
- tmpBuilder = new StringBuilder();
- int lineCount = 0;
- int length = tmpStr.length();
- for (int i = 0; i < length; i++) {
- tmpBuilder.append(tmpStr.charAt(i));
- lineCount++;
- if (lineCount > 72) {
- tmpBuilder.append(VCARD_COL_SEPARATOR);
- tmpBuilder.append(VCARD_WS);
- lineCount = 0;
- }
- }
- builder.append(tmpBuilder.toString());
- builder.append(VCARD_COL_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardPostalLine(final StringBuilder builder,
- final Integer typeAsObject, final String label,
- final ContentValues contentValues) {
- builder.append(VCARD_PROPERTY_ADR);
- builder.append(VCARD_ATTR_SEPARATOR);
-
- // Note: Not sure why we need to emit "empty" line even when actual data does not exist.
- // There may be some reason or may not be any. We keep safer side.
- // TODO: investigate this.
- boolean dataExists = false;
- String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
- boolean actuallyUseQuotedPrintable = false;
- boolean shouldAppendCharset = false;
- for (String data : dataArray) {
- if (!TextUtils.isEmpty(data)) {
- dataExists = true;
- if (!shouldAppendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
- shouldAppendCharset = true;
- }
- if (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
- actuallyUseQuotedPrintable = true;
- break;
- }
- }
- }
-
- int length = dataArray.length;
- for (int i = 0; i < length; i++) {
- String data = dataArray[i];
- if (!TextUtils.isEmpty(data)) {
- if (actuallyUseQuotedPrintable) {
- dataArray[i] = encodeQuotedPrintable(data);
- } else {
- dataArray[i] = escapeCharacters(data);
- }
- }
- }
-
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = StructuredPostal.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
- }
-
- String typeAsString = null;
- switch (typeAsPrimitive) {
- case StructuredPostal.TYPE_HOME: {
- typeAsString = Constants.ATTR_TYPE_HOME;
- break;
- }
- case StructuredPostal.TYPE_WORK: {
- typeAsString = Constants.ATTR_TYPE_WORK;
- break;
- }
- case StructuredPostal.TYPE_CUSTOM: {
- if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
- && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- // We're not sure whether the label is valid in the spec
- // ("IANA-token" in the vCard 3.0 is unclear...)
- // Just for safety, we add "X-" at the beggining of each label.
- // Also checks the label obeys with vCard 3.0 spec.
- builder.append("X-");
- builder.append(label);
- builder.append(VCARD_DATA_SEPARATOR);
- }
- break;
- }
- case StructuredPostal.TYPE_OTHER: {
- break;
- }
- default: {
- Log.e(LOG_TAG, "Unknown StructuredPostal type: " + typeAsPrimitive);
- break;
- }
- }
-
- // Attribute(s).
-
- {
- boolean shouldAppendAttrSeparator = false;
- if (typeAsString != null) {
- appendTypeAttribute(builder, typeAsString);
- shouldAppendAttrSeparator = true;
- }
-
- if (dataExists) {
- if (shouldAppendCharset) {
- // Strictly, vCard 3.0 does not allow exporters to emit charset information,
- // but we will add it since the information should be useful for importers,
- //
- // Assume no parser does not emit error with this attribute in vCard 3.0.
- if (shouldAppendAttrSeparator) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- builder.append(mVCardAttributeCharset);
- shouldAppendAttrSeparator = true;
- }
-
- if (actuallyUseQuotedPrintable) {
- if (shouldAppendAttrSeparator) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- builder.append(VCARD_ATTR_ENCODING_QP);
- shouldAppendAttrSeparator = true;
- }
- }
- }
-
- // Property values.
-
- builder.append(VCARD_DATA_SEPARATOR);
- if (dataExists) {
- // The elements in dataArray are already encoded to quoted printable
- // if needed.
- // See above.
- //
- // TODO: in vCard 3.0, one line may become too huge. Fix this.
- builder.append(dataArray[0]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[1]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[2]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[3]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[4]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[5]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[6]);
- }
- builder.append(VCARD_COL_SEPARATOR);
+ private final String toRfc2455Format(final long millSecs) {
+ Time startDate = new Time();
+ startDate.set(millSecs);
+ String date = startDate.format2445();
+ return date + FLAG_TIMEZONE_UTC;
}
- private void appendVCardEmailLine(final StringBuilder builder,
- final Integer typeAsObject, final String label, final String data) {
- builder.append(VCARD_PROPERTY_EMAIL);
-
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = Email.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
- }
-
- final String typeAsString;
- switch (typeAsPrimitive) {
- case Email.TYPE_CUSTOM: {
- // For backward compatibility.
- // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
- // To support mobile type at that time, this custom label had been used.
- if (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME
- .equals(label)) {
- typeAsString = Constants.ATTR_TYPE_CELL;
- } else if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
- && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- typeAsString = "X-" + label;
- } else {
- typeAsString = DEFAULT_EMAIL_TYPE;
- }
- break;
- }
- case Email.TYPE_HOME: {
- typeAsString = Constants.ATTR_TYPE_HOME;
- break;
- }
- case Email.TYPE_WORK: {
- typeAsString = Constants.ATTR_TYPE_WORK;
+ /**
+ * Try to append the property line for a call history time stamp field if possible.
+ * Do nothing if the call log type gotton from the database is invalid.
+ */
+ private void tryAppendCallHistoryTimeStampField(final VCardBuilder builder) {
+ // Extension for call history as defined in
+ // in the Specification for Ic Mobile Communcation - ver 1.1,
+ // Oct 2000. This is used to send the details of the call
+ // history - missed, incoming, outgoing along with date and time
+ // to the requesting device (For example, transferring phone book
+ // when connected over bluetooth)
+ //
+ // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
+ final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
+ final String callLogTypeStr;
+ switch (callLogType) {
+ case Calls.INCOMING_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
break;
}
- case Email.TYPE_OTHER: {
- typeAsString = DEFAULT_EMAIL_TYPE;
+ case Calls.OUTGOING_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
break;
}
- case Email.TYPE_MOBILE: {
- typeAsString = Constants.ATTR_TYPE_CELL;
+ case Calls.MISSED_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
break;
}
default: {
- Log.e(LOG_TAG, "Unknown Email type: " + typeAsPrimitive);
- typeAsString = DEFAULT_EMAIL_TYPE;
- break;
- }
- }
-
- builder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(builder, typeAsString);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(data);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardTelephoneLine(final StringBuilder builder,
- final Integer typeAsObject, final String label,
- String encodedData) {
- builder.append(VCARD_PROPERTY_TEL);
- builder.append(VCARD_ATTR_SEPARATOR);
-
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = Phone.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
- }
-
- switch (typeAsPrimitive) {
- case Phone.TYPE_HOME:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE));
- break;
- case Phone.TYPE_WORK:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE));
- break;
- case Phone.TYPE_FAX_HOME:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX));
- break;
- case Phone.TYPE_FAX_WORK:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX));
- break;
- case Phone.TYPE_MOBILE:
- builder.append(Constants.ATTR_TYPE_CELL);
- break;
- case Phone.TYPE_PAGER:
- if (mIsDoCoMo) {
- // Not sure about the reason, but previous implementation had
- // used "VOICE" instead of "PAGER"
- // Also, refrain from using appendType() so that "TYPE=" is never be appended.
- builder.append(Constants.ATTR_TYPE_VOICE);
- } else {
- appendTypeAttribute(builder, Constants.ATTR_TYPE_PAGER);
- }
- break;
- case Phone.TYPE_OTHER:
- appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
- break;
- case Phone.TYPE_CUSTOM:
- if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
- && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- appendTypeAttribute(builder, "X-" + label);
- } else {
- // Just ignore the custom type.
- appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
- }
- break;
- default:
- appendUncommonPhoneType(builder, typeAsPrimitive);
- break;
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedData);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- /**
- * Appends phone type string which may not be available in some devices.
- */
- private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
- if (mIsDoCoMo) {
- // The previous implementation for DoCoMo had been conservative
- // about miscellaneous types.
- builder.append(Constants.ATTR_TYPE_VOICE);
- } else {
- String phoneAttribute = VCardUtils.getPhoneAttributeString(type);
- if (phoneAttribute != null) {
- appendTypeAttribute(builder, phoneAttribute);
- } else {
- Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
- }
- }
- }
-
- private void appendVCardLine(final StringBuilder builder,
- final String propertyName, final String rawData) {
- appendVCardLine(builder, propertyName, rawData, false, false);
- }
-
- private void appendVCardLine(final StringBuilder builder,
- final String field, final String rawData, final boolean needCharset,
- boolean needQuotedPrintable) {
- builder.append(field);
- if (needCharset) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
-
- final String encodedData;
- if (needQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- encodedData = encodeQuotedPrintable(rawData);
- } 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.
- encodedData = escapeCharacters(rawData);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedData);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendTypeAttributes(final StringBuilder builder,
- final List<String> types) {
- // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
- // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
- boolean first = true;
- for (String type : types) {
- if (first) {
- first = false;
- } else {
- builder.append(VCARD_ATTR_SEPARATOR);
+ Log.w(LOG_TAG, "Call log type not correct.");
+ return;
}
- appendTypeAttribute(builder, type);
- }
- }
-
- private void appendTypeAttribute(final StringBuilder builder, final String type) {
- // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
- if (mIsV30) {
- builder.append(Constants.ATTR_TYPE).append(VCARD_ATTR_EQUAL);
}
- builder.append(type);
- }
-
- /**
- * Returns true when the property line should contain charset attribute
- * information. This method may return true even when vCard version is 3.0.
- *
- * Strictly, adding charset information is invalid in VCard 3.0.
- * However we'll add the info only when used charset is not UTF-8
- * in vCard 3.0 format, since parser side may be able to use the charset
- * via this field, though we may encounter another problem by adding it...
- *
- * e.g. Japanese mobile phones use Shift_Jis while RFC 2426
- * recommends UTF-8. By adding this field, parsers may be able
- * to know this text is NOT UTF-8 but Shift_Jis.
- */
- private boolean shouldAppendCharsetAttribute(final String propertyValue) {
- return (!VCardUtils.containsOnlyPrintableAscii(propertyValue) &&
- (!mIsV30 || !mUsesUtf8));
- }
- private boolean shouldAppendCharsetAttribute(final List<String> propertyValueList) {
- boolean shouldAppendBasically = false;
- for (String propertyValue : propertyValueList) {
- if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
- shouldAppendBasically = true;
- break;
- }
- }
- return shouldAppendBasically && (!mIsV30 || !mUsesUtf8);
+ final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
+ builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP,
+ Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong));
}
- private String encodeQuotedPrintable(String str) {
- if (TextUtils.isEmpty(str)) {
- return "";
- }
- {
- // Replace "\n" and "\r" with "\r\n".
- StringBuilder tmpBuilder = new StringBuilder();
- int length = str.length();
- for (int i = 0; i < length; i++) {
- char ch = str.charAt(i);
- if (ch == '\r') {
- if (i + 1 < length && str.charAt(i + 1) == '\n') {
- i++;
- }
- tmpBuilder.append("\r\n");
- } else if (ch == '\n') {
- tmpBuilder.append("\r\n");
- } else {
- tmpBuilder.append(ch);
- }
- }
- str = tmpBuilder.toString();
+ private String createOneCallLogEntryInternal() {
+ final VCardBuilder builder = new VCardBuilder(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
+ if (TextUtils.isEmpty(name)) {
+ name = mCursor.getString(NUMBER_COLUMN_INDEX);
}
+ final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
+ builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false);
+ builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false);
- final StringBuilder tmpBuilder = new StringBuilder();
- int index = 0;
- int lineCount = 0;
- byte[] strArray = null;
-
- try {
- strArray = str.getBytes(mCharsetString);
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
- + "Try default charset");
- strArray = str.getBytes();
- }
- while (index < strArray.length) {
- tmpBuilder.append(String.format("=%02X", strArray[index]));
- index += 1;
- lineCount += 3;
-
- if (lineCount >= 67) {
- // Specification requires CRLF must be inserted before the
- // length of the line
- // becomes more than 76.
- // Assuming that the next character is a multi-byte character,
- // it will become
- // 6 bytes.
- // 76 - 6 - 3 = 67
- tmpBuilder.append("=\r\n");
- lineCount = 0;
- }
+ final String number = mCursor.getString(NUMBER_COLUMN_INDEX);
+ final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+ String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
+ if (TextUtils.isEmpty(label)) {
+ label = Integer.toString(type);
}
-
- return tmpBuilder.toString();
+ builder.appendTelLine(type, label, number, false);
+ tryAppendCallHistoryTimeStampField(builder);
+ return builder.toString();
}
}
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 68cd0df..ecd089a 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,16 +15,19 @@
*/
package android.pim.vcard;
+import android.util.Log;
+
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
/**
* The class representing VCard related configurations. Useful static methods are not in this class
* but in VCardUtils.
*/
public class VCardConfig {
- // TODO: may be better to make the instance of this available and stop using static methods and
- // one integer.
+ private static final String LOG_TAG = "VCardConfig";
/* package */ static final int LOG_LEVEL_NONE = 0;
/* package */ static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
@@ -34,15 +37,18 @@ public class VCardConfig {
/* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
+ /* package */ static final int PARSE_TYPE_UNKNOWN = 0;
+ /* package */ static final int PARSE_TYPE_APPLE = 1;
+ /* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2; // For Japanese mobile phones.
+ /* 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";
- // TODO: make the other codes use this flag
- public static final boolean IGNORE_CASE_EXCEPT_VALUE = true;
-
- private static final int FLAG_V21 = 0;
- private static final int FLAG_V30 = 1;
+ public static final int FLAG_V21 = 0;
+ public static final int FLAG_V30 = 1;
// 0x2 is reserved for the future use ...
@@ -54,7 +60,8 @@ public class VCardConfig {
// 0x10 is reserved for safety
private static final int FLAG_CHARSET_UTF8 = 0;
- private static final int FLAG_CHARSET_SHIFT_JIS = 0x20;
+ private static final int FLAG_CHARSET_SHIFT_JIS = 0x100;
+ private static final int FLAG_CHARSET_MASK = 0xF00;
/**
* The flag indicating the vCard composer will add some "X-" properties used only in Android
@@ -95,96 +102,196 @@ public class VCardConfig {
private static final int FLAG_DOCOMO = 0x20000000;
/**
- * The flag indicating the vCard composer use Quoted-Printable toward even "primary" types.
- * In this context, "primary" types means "N", "FN", etc. which are usually "not" encoded
- * into Quoted-Printable format in external exporters.
- * This flag is useful when some target importer does not accept "primary" property values
- * without Quoted-Printable encoding.
- *
- * @hide Temporaly made public. We don't strictly define "primary", so we may change the
- * behavior around this flag in the future. Do not use this flag without any reason.
+ * <P>
+ * The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
+ * properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
+ * </P>
+ * <P>
+ * We actually cannot define what is the "primary" property. Note that this is NOT defined
+ * in vCard specification either. Also be aware that it is NOT related to "primary" notion
+ * used in {@link android.provider.ContactsContract}.
+ * This notion is just for vCard composition in Android.
+ * </P>
+ * <P>
+ * We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
+ * do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
+ * even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
+ * other properties like "ADR", "ORG", etc.
+ * <P>
+ * We are afraid of the case where some vCard importer also forget handling QP presuming QP is
+ * not used in such fields.
+ * </P>
+ * <P>
+ * This flag is useful when some target importer you are going to focus on does not accept
+ * such properties with Quoted-Printable encoding.
+ * </P>
+ * <P>
+ * Again, we should not use this flag at all for complying vCard 2.1 spec.
+ * </P>
+ * <P>
+ * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
+ * kind of problem (hopefully).
+ * </P>
*/
- public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
-
- // VCard types
+ public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
/**
- * General vCard format with the version 2.1. Uses UTF-8 for the charset.
- * When composing a vCard entry, the US convension will be used.
- *
+ * <P>
+ * The flag indicating that phonetic name related fields must be converted to
+ * appropriate form. Note that "appropriate" is not defined in any vCard specification.
+ * This is Android-specific.
+ * </P>
+ * <P>
+ * One typical (and currently sole) example where we need this flag is the time when
+ * we need to emit Japanese phonetic names into vCard entries. The property values
+ * should be encoded into half-width katakana when the target importer is Japanese mobile
+ * phones', which are probably not able to parse full-width hiragana/katakana for
+ * historical reasons, while the vCard importers embedded to softwares for PC should be
+ * able to parse them as we expect.
+ * </P>
+ */
+ public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
+
+ /**
+ * <P>
+ * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
+ * every time possible. The default behavior does not emit it and is valid in the spec.
+ * In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
+ * </P>
+ * <P>
+ * Detail:
+ * How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
+ * </p>
+ * <P>
+ * e.g.<BR />
+ * 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR />
+ * 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR />
+ * 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR />
+ * </P>
+ * <P>
+ * 2) had been the default of VCard exporter/importer in Android, but it is found that
+ * some external exporter is not able to parse the type format like 2) but only 3).
+ * </P>
+ * <P>
+ * If you are targeting to the importer which cannot accept TYPE params without "TYPE="
+ * strings (which should be rare though), please use this flag.
+ * </P>
+ * <P>
+ * Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
+ * </P>
+ */
+ public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
+
+ //// The followings are VCard types available from importer/exporter. ////
+
+ /**
+ * <P>
+ * Generic vCard format with the vCard 2.1. Uses UTF-8 for the charset.
+ * When composing a vCard entry, the US convension will be used toward formatting
+ * some values.
+ * </P>
+ * <P>
* e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
- * while in Japan, it should be "Prefix Family Middle Given Suffix".
+ * while it should be "Prefix Family Middle Given Suffix" in Japan for example.
+ * </P>
*/
- public static final int VCARD_TYPE_V21_GENERIC =
+ public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
(FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
+ /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
/**
+ * <P>
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
- *
- * Note that this type is not fully implemented, so probably some bugs remain both in
- * parsing and composing.
- *
- * TODO: implement this type correctly.
+ * </P>
+ * <P>
+ * Not fully ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_GENERIC =
+ public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
(FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
/**
- * General vCard format with the version 2.1 with some Europe convension. Uses Utf-8.
+ * <P>
+ * General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
* Currently, only name order is considered ("Prefix Middle Given Family Suffix")
+ * </P>
*/
- public static final int VCARD_TYPE_V21_EUROPE =
+ public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
(FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
/**
- * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8
+ * <P>
+ * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_EUROPE =
+ public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
(FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
-
- /**
- * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
- * parsing/composing the vCard data.
- */
- public static final int VCARD_TYPE_V21_JAPANESE =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese";
-
/**
- * vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * <P>
+ * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
(FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
+
+ /**
+ * <P>
+ * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
+ * parsing/composing the vCard data.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
/**
+ * <P>
* vCard format for miscellaneous Japanese devices, using Shift_Jis for
* parsing/composing the vCard data.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_JAPANESE =
+ public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese";
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
/**
- * vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * <P>
+ * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
@@ -193,111 +300,137 @@ public class VCardConfig {
/* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
/**
- * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
- * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
- * No Android-specific property nor defact property is included.
+ * <P>
+ * The vCard 2.1 based format which (partially) considers the convention in Japanese
+ * mobile phones, where phonetic names are translated to half-width katakana if
+ * possible, etc.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_CONVERT_PHONETIC_NAME_STRINGS |
+ FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
+
+ /**
+ * <P>
+ * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+ * </p>
+ * <P>
+ * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
+ * No Android-specific property nor defact property is included. The "Primary" properties
+ * are NOT encoded to Quoted-Printable.
+ * </P>
*/
public static final int VCARD_TYPE_DOCOMO =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | FLAG_DOCOMO);
+ (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
- private static final String VCARD_TYPE_DOCOMO_STR = "docomo";
+ /* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
- public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
+ public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8;
- private static final Map<String, Integer> VCARD_TYPES_MAP;
+ private static final Map<String, Integer> sVCardTypeMap;
+ private static final Set<Integer> sJapaneseMobileTypeSet;
static {
- VCARD_TYPES_MAP = new HashMap<String, Integer>();
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
- VCARD_TYPES_MAP.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+ sVCardTypeMap = new HashMap<String, Integer>();
+ sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+
+ sJapaneseMobileTypeSet = new HashSet<Integer>();
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
}
- public static int getVCardTypeFromString(String vcardTypeString) {
- String loweredKey = vcardTypeString.toLowerCase();
- if (VCARD_TYPES_MAP.containsKey(loweredKey)) {
- return VCARD_TYPES_MAP.get(loweredKey);
+ public static int getVCardTypeFromString(final String vcardTypeString) {
+ final String loweredKey = vcardTypeString.toLowerCase();
+ if (sVCardTypeMap.containsKey(loweredKey)) {
+ return sVCardTypeMap.get(loweredKey);
} else {
- // XXX: should return the value indicating the input is invalid?
+ Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
return VCARD_TYPE_DEFAULT;
}
}
- public static boolean isV30(int vcardType) {
+ public static boolean isV30(final int vcardType) {
return ((vcardType & FLAG_V30) != 0);
}
- public static boolean usesQuotedPrintable(int vcardType) {
+ public static boolean shouldUseQuotedPrintable(final int vcardType) {
return !isV30(vcardType);
}
- public static boolean isDoCoMo(int vcardType) {
- return ((vcardType & FLAG_DOCOMO) != 0);
- }
-
- /**
- * @return true if the device is Japanese and some Japanese convension is
- * applied to creating "formatted" something like FORMATTED_ADDRESS.
- */
- public static boolean isJapaneseDevice(int vcardType) {
- return ((vcardType == VCARD_TYPE_V21_JAPANESE) ||
- (vcardType == VCARD_TYPE_V21_JAPANESE_UTF8) ||
- (vcardType == VCARD_TYPE_V30_JAPANESE) ||
- (vcardType == VCARD_TYPE_V30_JAPANESE_UTF8) ||
- (vcardType == VCARD_TYPE_DOCOMO));
+ public static boolean usesUtf8(final int vcardType) {
+ return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8);
}
- public static boolean usesUtf8(int vcardType) {
- return ((vcardType & FLAG_CHARSET_UTF8) != 0);
+ public static boolean usesShiftJis(final int vcardType) {
+ return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS);
}
- public static boolean usesShiftJis(int vcardType) {
- return ((vcardType & FLAG_CHARSET_SHIFT_JIS) != 0);
- }
-
- /**
- * @return true when Japanese phonetic string must be converted to a string
- * containing only half-width katakana. This method exists since Japanese mobile
- * phones usually use only half-width katakana for expressing phonetic names and
- * some devices are not ready for parsing other phonetic strings like hiragana and
- * full-width katakana.
- */
- public static boolean needsToConvertPhoneticString(int vcardType) {
- return (vcardType == VCARD_TYPE_DOCOMO);
- }
-
- public static int getNameOrderType(int vcardType) {
+ public static int getNameOrderType(final int vcardType) {
return vcardType & NAME_ORDER_MASK;
}
- public static boolean usesAndroidSpecificProperty(int vcardType) {
+ public static boolean usesAndroidSpecificProperty(final int vcardType) {
return ((vcardType & FLAG_USE_ANDROID_PROPERTY) != 0);
}
- public static boolean usesDefactProperty(int vcardType) {
+ public static boolean usesDefactProperty(final int vcardType) {
return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
}
- public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
- return vcardType == VCARD_TYPE_DOCOMO;
- }
-
public static boolean showPerformanceLog() {
return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
}
+ public static boolean shouldRefrainQPToNameProperties(final int vcardType) {
+ return (!shouldUseQuotedPrintable(vcardType) ||
+ ((vcardType & FLAG_REFRAIN_QP_TO_NAME_PROPERTIES) != 0));
+ }
+
+ public static boolean appendTypeParamName(final int vcardType) {
+ return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
+ }
+
/**
- * @hide
+ * @return true if the device is Japanese and some Japanese convension is
+ * applied to creating "formatted" something like FORMATTED_ADDRESS.
*/
- public static boolean usesQPToPrimaryProperties(int vcardType) {
- return (usesQuotedPrintable(vcardType) &&
- ((vcardType & FLAG_USE_QP_TO_PRIMARY_PROPERTIES) != 0));
+ public static boolean isJapaneseDevice(final int vcardType) {
+ // TODO: Some mask will be required so that this method wrongly interpret
+ // Japanese"-like" vCard type.
+ // e.g. VCARD_TYPE_V21_JAPANESE_SJIS | FLAG_APPEND_TYPE_PARAMS
+ return sJapaneseMobileTypeSet.contains(vcardType);
+ }
+
+ public static boolean needsToConvertPhoneticString(final int vcardType) {
+ return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
+ }
+
+ public static boolean onlyOneNoteFieldIsAvailable(final int vcardType) {
+ return vcardType == VCARD_TYPE_DOCOMO;
+ }
+
+ public static boolean isDoCoMo(final int vcardType) {
+ return ((vcardType & FLAG_DOCOMO) != 0);
}
private VCardConfig() {
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
new file mode 100644
index 0000000..8c07126
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -0,0 +1,152 @@
+/*
+ * 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 android.pim.vcard;
+
+/**
+ * Constants used in both exporter and importer code.
+ */
+public class VCardConstants {
+ public static final String VERSION_V21 = "2.1";
+ public static final String VERSION_V30 = "3.0";
+
+ // The property names valid both in vCard 2.1 and 3.0.
+ public static final String PROPERTY_BEGIN = "BEGIN";
+ public static final String PROPERTY_VERSION = "VERSION";
+ public static final String PROPERTY_N = "N";
+ public static final String PROPERTY_FN = "FN";
+ public static final String PROPERTY_ADR = "ADR";
+ public static final String PROPERTY_EMAIL = "EMAIL";
+ public static final String PROPERTY_NOTE = "NOTE";
+ public static final String PROPERTY_ORG = "ORG";
+ public static final String PROPERTY_SOUND = "SOUND"; // Not fully supported.
+ public static final String PROPERTY_TEL = "TEL";
+ public static final String PROPERTY_TITLE = "TITLE";
+ public static final String PROPERTY_ROLE = "ROLE";
+ public static final String PROPERTY_PHOTO = "PHOTO";
+ public static final String PROPERTY_LOGO = "LOGO";
+ public static final String PROPERTY_URL = "URL";
+ public static final String PROPERTY_BDAY = "BDAY"; // Birthday
+ public static final String PROPERTY_END = "END";
+
+ // Valid property names not supported (not appropriately handled) by our vCard importer now.
+ public static final String PROPERTY_REV = "REV";
+ public static final String PROPERTY_AGENT = "AGENT";
+
+ // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
+ public static final String PROPERTY_NAME = "NAME";
+ public static final String PROPERTY_NICKNAME = "NICKNAME";
+ public static final String PROPERTY_SORT_STRING = "SORT-STRING";
+
+ // De-fact property values expressing phonetic names.
+ public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
+ public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
+ public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
+
+ // Properties both ContactsStruct in Eclair and de-fact vCard extensions
+ // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
+ public static final String PROPERTY_X_AIM = "X-AIM";
+ public static final String PROPERTY_X_MSN = "X-MSN";
+ public static final String PROPERTY_X_YAHOO = "X-YAHOO";
+ public static final String PROPERTY_X_ICQ = "X-ICQ";
+ public static final String PROPERTY_X_JABBER = "X-JABBER";
+ public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
+ public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
+ // Properties only ContactsStruct has. We alse use this.
+ public static final String PROPERTY_X_QQ = "X-QQ";
+ public static final String PROPERTY_X_NETMEETING = "X-NETMEETING";
+
+ // Phone number for Skype, available as usual phone.
+ public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
+
+ // Property for Android-specific fields.
+ public static final String PROPERTY_X_ANDROID_CUSTOM = "X-ANDROID-CUSTOM";
+
+ // Properties for DoCoMo vCard.
+ public static final String PROPERTY_X_CLASS = "X-CLASS";
+ public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
+ public static final String PROPERTY_X_NO = "X-NO";
+ public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
+
+ public static final String PARAM_TYPE = "TYPE";
+
+ public static final String PARAM_TYPE_HOME = "HOME";
+ public static final String PARAM_TYPE_WORK = "WORK";
+ public static final String PARAM_TYPE_FAX = "FAX";
+ public static final String PARAM_TYPE_CELL = "CELL";
+ public static final String PARAM_TYPE_VOICE = "VOICE";
+ public static final String PARAM_TYPE_INTERNET = "INTERNET";
+
+ // Abbreviation of "prefered" according to vCard 2.1 specification.
+ // We interpret this value as "primary" property during import/export.
+ //
+ // Note: Both vCard specs does not mention anything about the requirement for this parameter,
+ // but there may be some vCard importer which will get confused with more than
+ // one "PREF"s in one property name, while Android accepts them.
+ public static final String PARAM_TYPE_PREF = "PREF";
+
+ // Phone type parameters valid in vCard and known to ContactsContract, but not so common.
+ public static final String PARAM_TYPE_CAR = "CAR";
+ public static final String PARAM_TYPE_ISDN = "ISDN";
+ public static final String PARAM_TYPE_PAGER = "PAGER";
+ public static final String PARAM_TYPE_TLX = "TLX"; // Telex
+
+ // Phone types existing in vCard 2.1 but not known to ContactsContract.
+ public static final String PARAM_TYPE_MODEM = "MODEM";
+ public static final String PARAM_TYPE_MSG = "MSG";
+ public static final String PARAM_TYPE_BBS = "BBS";
+ public static final String PARAM_TYPE_VIDEO = "VIDEO";
+
+ // 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.
+ public static final String PARAM_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK";
+ public static final String PARAM_PHONE_EXTRA_TYPE_RADIO = "RADIO";
+ public static final String PARAM_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
+ public static final String PARAM_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
+ // vCard composer translates this type to "WORK" + "PREF". Just for parsing.
+ public static final String PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
+ // vCard composer translates this type to "VOICE" Just for parsing.
+ public static final String PARAM_PHONE_EXTRA_TYPE_OTHER = "OTHER";
+
+ // TYPE parameters for postal addresses.
+ public static final String PARAM_ADR_TYPE_PARCEL = "PARCEL";
+ public static final String PARAM_ADR_TYPE_DOM = "DOM";
+ public static final String PARAM_ADR_TYPE_INTL = "INTL";
+
+ // TYPE parameters not officially valid but used in some vCard exporter.
+ // 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,
+ // which is specifically invalid but should be always properly accepted, and emitted
+ // in some special case (for that device/application).
+ public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
+ }
+
+ /* package */ static final int MAX_DATA_COLUMN = 15;
+
+ /* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
+ static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
+
+ private VCardConstants() {
+ }
+} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/VCardEntry.java
index 36e5e23..20eee84 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -20,8 +20,10 @@ import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.OperationApplicationException;
import android.database.Cursor;
+import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
@@ -44,36 +46,37 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* This class bridges between data structure of Contact app and VCard data.
*/
-public class ContactStruct {
- private static final String LOG_TAG = "vcard.ContactStruct";
-
- // Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ"
- // Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
+public class VCardEntry {
+ private static final String LOG_TAG = "VCardEntry";
+
+ private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+ private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
+ private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
+
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
-
+
static {
- sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
- sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
- sImMap.put(Constants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
- sImMap.put(Constants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
- sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
- sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
- sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
- sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
+ sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
+ sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
+ sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
+ sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
+ sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
+ sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
+ Im.PROTOCOL_GOOGLE_TALK);
}
-
- /**
- * @hide only for testing
- */
+
static public class PhoneData {
public final int type;
public final String data;
@@ -90,7 +93,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof PhoneData) {
+ if (!(obj instanceof PhoneData)) {
return false;
}
PhoneData phoneData = (PhoneData)obj;
@@ -105,9 +108,6 @@ public class ContactStruct {
}
}
- /**
- * @hide only for testing
- */
static public class EmailData {
public final int type;
public final String data;
@@ -125,7 +125,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof EmailData) {
+ if (!(obj instanceof EmailData)) {
return false;
}
EmailData emailData = (EmailData)obj;
@@ -152,17 +152,12 @@ public class ContactStruct {
public final String region;
public final String postalCode;
public final String country;
-
public final int type;
-
- // Used only when type variable is TYPE_CUSTOM.
public final String label;
-
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
public boolean isPrimary;
- public PostalData(int type, List<String> propValueList,
- String label, boolean isPrimary) {
+
+ public PostalData(final int type, final List<String> propValueList,
+ final String label, boolean isPrimary) {
this.type = type;
dataArray = new String[ADDR_MAX_DATA_SIZE];
@@ -171,9 +166,9 @@ public class ContactStruct {
size = ADDR_MAX_DATA_SIZE;
}
- // adr-value = 0*6(text-value ";") text-value
- // ; PO Box, Extended Address, Street, Locality, Region, Postal
- // ; Code, Country Name
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
//
// Use Iterator assuming List may be LinkedList, though actually it is
// always ArrayList in the current implementation.
@@ -195,25 +190,24 @@ public class ContactStruct {
this.region = dataArray[4];
this.postalCode = dataArray[5];
this.country = dataArray[6];
-
this.label = label;
this.isPrimary = isPrimary;
}
@Override
public boolean equals(Object obj) {
- if (obj instanceof PostalData) {
+ if (!(obj instanceof PostalData)) {
return false;
}
- PostalData postalData = (PostalData)obj;
+ final PostalData postalData = (PostalData)obj;
return (Arrays.equals(dataArray, postalData.dataArray) &&
(type == postalData.type &&
(type == StructuredPostal.TYPE_CUSTOM ?
(label == postalData.label) : true)) &&
(isPrimary == postalData.isPrimary));
}
-
- public String getFormattedAddress(int vcardType) {
+
+ public String getFormattedAddress(final int vcardType) {
StringBuilder builder = new StringBuilder();
boolean empty = true;
if (VCardConfig.isJapaneseDevice(vcardType)) {
@@ -223,9 +217,10 @@ public class ContactStruct {
if (!TextUtils.isEmpty(addressPart)) {
if (!empty) {
builder.append(' ');
+ } else {
+ empty = false;
}
builder.append(addressPart);
- empty = false;
}
}
} else {
@@ -234,9 +229,10 @@ public class ContactStruct {
if (!TextUtils.isEmpty(addressPart)) {
if (!empty) {
builder.append(' ');
+ } else {
+ empty = false;
}
builder.append(addressPart);
- empty = false;
}
}
}
@@ -250,102 +246,127 @@ public class ContactStruct {
type, label, isPrimary);
}
}
-
- /**
- * @hide only for testing.
- */
+
static public class OrganizationData {
public final int type;
- public final String companyName;
- // can be changed in some VCard format.
- public String positionName;
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
+ // non-final is Intentional: we may change the values since this info is separated into
+ // two parts in vCard: "ORG" + "TITLE".
+ public String companyName;
+ public String departmentName;
+ public String titleName;
public boolean isPrimary;
- public OrganizationData(int type, String companyName, String positionName,
+
+ public OrganizationData(int type,
+ String companyName,
+ String departmentName,
+ String titleName,
boolean isPrimary) {
this.type = type;
this.companyName = companyName;
- this.positionName = positionName;
+ this.departmentName = departmentName;
+ this.titleName = titleName;
this.isPrimary = isPrimary;
}
@Override
public boolean equals(Object obj) {
- if (obj instanceof OrganizationData) {
+ if (!(obj instanceof OrganizationData)) {
return false;
}
OrganizationData organization = (OrganizationData)obj;
- return (type == organization.type && companyName.equals(organization.companyName) &&
- positionName.equals(organization.positionName) &&
+ return (type == organization.type &&
+ TextUtils.equals(companyName, organization.companyName) &&
+ TextUtils.equals(departmentName, organization.departmentName) &&
+ TextUtils.equals(titleName, organization.titleName) &&
isPrimary == organization.isPrimary);
}
-
+
@Override
public String toString() {
- return String.format("type: %d, company: %s, position: %s, isPrimary: %s",
- type, companyName, positionName, isPrimary);
+ return String.format(
+ "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
+ type, companyName, departmentName, titleName, isPrimary);
}
}
static public class ImData {
+ public final int protocol;
+ public final String customProtocol;
public final int type;
public final String data;
- public final String label;
public final boolean isPrimary;
-
- // TODO: ContactsConstant#PROTOCOL, ContactsConstant#CUSTOM_PROTOCOL should be used?
- public ImData(int type, String data, String label, boolean isPrimary) {
+
+ public ImData(final int protocol, final String customProtocol, final int type,
+ final String data, final boolean isPrimary) {
+ this.protocol = protocol;
+ this.customProtocol = customProtocol;
this.type = type;
this.data = data;
- this.label = label;
this.isPrimary = isPrimary;
}
-
+
@Override
public boolean equals(Object obj) {
- if (obj instanceof ImData) {
+ if (!(obj instanceof ImData)) {
return false;
}
ImData imData = (ImData)obj;
- return (type == imData.type && data.equals(imData.data) &&
- label.equals(imData.label) && isPrimary == imData.isPrimary);
+ return (type == imData.type && protocol == imData.protocol
+ && (customProtocol != null ? customProtocol.equals(imData.customProtocol) :
+ (imData.customProtocol == null))
+ && (data != null ? data.equals(imData.data) : (imData.data == null))
+ && isPrimary == imData.isPrimary);
}
-
+
@Override
public String toString() {
- return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
- type, data, label, isPrimary);
+ return String.format(
+ "type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s",
+ type, protocol, customProtocol, data, isPrimary);
}
}
- /**
- * @hide only for testing.
- */
- static public class PhotoData {
+ public static class PhotoData {
public static final String FORMAT_FLASH = "SWF";
public final int type;
public final String formatName; // used when type is not defined in ContactsContract.
public final byte[] photoBytes;
+ public final boolean isPrimary;
- public PhotoData(int type, String formatName, byte[] photoBytes) {
+ public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) {
this.type = type;
this.formatName = formatName;
this.photoBytes = photoBytes;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PhotoData)) {
+ return false;
+ }
+ PhotoData photoData = (PhotoData)obj;
+ return (type == photoData.type &&
+ (formatName == null ? (photoData.formatName == null) :
+ formatName.equals(photoData.formatName)) &&
+ (Arrays.equals(photoBytes, photoData.photoBytes)) &&
+ (isPrimary == photoData.isPrimary));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, format: %s: size: %d, isPrimary: %s",
+ type, formatName, photoBytes.length, isPrimary);
}
}
- static /* package */ class Property {
+ /* package */ static class Property {
private String mPropertyName;
private Map<String, Collection<String>> mParameterMap =
new HashMap<String, Collection<String>>();
private List<String> mPropertyValueList = new ArrayList<String>();
private byte[] mPropertyBytes;
-
- public Property() {
- clear();
- }
-
+
public void setPropertyName(final String propertyName) {
mPropertyName = propertyName;
}
@@ -385,6 +406,7 @@ public class ContactStruct {
mPropertyName = null;
mParameterMap.clear();
mPropertyValueList.clear();
+ mPropertyBytes = null;
}
}
@@ -417,236 +439,48 @@ public class ContactStruct {
private List<ImData> mImList;
private List<PhotoData> mPhotoList;
private List<String> mWebsiteList;
-
+ private List<List<String>> mAndroidCustomPropertyList;
+
private final int mVCardType;
private final Account mAccount;
- // Each Column of four properties has ISPRIMARY field
- // (See android.provider.Contacts)
- // If false even after the parsing loop, we choose the first entry as a "primary"
- // entry.
- private boolean mPrefIsSet_Address;
- private boolean mPrefIsSet_Phone;
- private boolean mPrefIsSet_Email;
- private boolean mPrefIsSet_Organization;
-
- public ContactStruct() {
- this(VCardConfig.VCARD_TYPE_V21_GENERIC);
+ public VCardEntry() {
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
}
- public ContactStruct(int vcardType) {
+ public VCardEntry(int vcardType) {
this(vcardType, null);
}
- public ContactStruct(int vcardType, Account account) {
+ public VCardEntry(int vcardType, Account account) {
mVCardType = vcardType;
mAccount = account;
}
- /**
- * @hide only for testing.
- */
- public ContactStruct(String givenName,
- String familyName,
- String middleName,
- String prefix,
- String suffix,
- String phoneticGivenName,
- String pheneticFamilyName,
- String phoneticMiddleName,
- List<byte[]> photoBytesList,
- List<String> notes,
- List<PhoneData> phoneList,
- List<EmailData> emailList,
- List<PostalData> postalList,
- List<OrganizationData> organizationList,
- List<PhotoData> photoList,
- List<String> websiteList) {
- this(VCardConfig.VCARD_TYPE_DEFAULT);
- mGivenName = givenName;
- mFamilyName = familyName;
- mPrefix = prefix;
- mSuffix = suffix;
- mPhoneticGivenName = givenName;
- mPhoneticFamilyName = familyName;
- mPhoneticMiddleName = middleName;
- mEmailList = emailList;
- mPostalList = postalList;
- mOrganizationList = organizationList;
- mPhotoList = photoList;
- mWebsiteList = websiteList;
- }
-
- // All getter methods should be used carefully, since they may change
- // in the future as of 2009-09-24, on which I cannot be sure this structure
- // is completely consolidated.
- // When we are sure we will no longer change them, we'll be happy to
- // make it complete public (withouth @hide tag)
- //
- // Also note that these getter methods should be used only after
- // all properties being pushed into this object. If not, incorrect
- // value will "be stored in the local cache and" be returned to you.
-
- /**
- * @hide
- */
- public String getFamilyName() {
- return mFamilyName;
- }
-
- /**
- * @hide
- */
- public String getGivenName() {
- return mGivenName;
- }
-
- /**
- * @hide
- */
- public String getMiddleName() {
- return mMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPrefix() {
- return mPrefix;
- }
-
- /**
- * @hide
- */
- public String getSuffix() {
- return mSuffix;
- }
-
- /**
- * @hide
- */
- public String getFullName() {
- return mFullName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFamilyName() {
- return mPhoneticFamilyName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticGivenName() {
- return mPhoneticGivenName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticMiddleName() {
- return mPhoneticMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFullName() {
- return mPhoneticFullName;
- }
-
- /**
- * @hide
- */
- public final List<String> getNickNameList() {
- return mNickNameList;
- }
-
- /**
- * @hide
- */
- public String getDisplayName() {
- if (mDisplayName == null) {
- constructDisplayName();
- }
- return mDisplayName;
- }
-
- /**
- * @hide
- */
- public String getBirthday() {
- return mBirthday;
- }
-
- /**
- * @hide
- */
- public final List<PhotoData> getPhotoList() {
- return mPhotoList;
- }
-
- /**
- * @hide
- */
- public final List<String> getNotes() {
- return mNoteList;
- }
-
- /**
- * @hide
- */
- public final List<PhoneData> getPhoneList() {
- return mPhoneList;
- }
-
- /**
- * @hide
- */
- public final List<EmailData> getEmailList() {
- return mEmailList;
- }
-
- /**
- * @hide
- */
- public final List<PostalData> getPostalList() {
- return mPostalList;
- }
-
- /**
- * @hide
- */
- public final List<OrganizationData> getOrganizationList() {
- return mOrganizationList;
- }
-
- /**
- * Add a phone info to phoneList.
- * @param data phone number
- * @param type type col of content://contacts/phones
- * @param label lable col of content://contacts/phones
- */
- private void addPhone(int type, String data, String label, boolean isPrimary){
+ private void addPhone(int type, String data, String label, boolean isPrimary) {
if (mPhoneList == null) {
mPhoneList = new ArrayList<PhoneData>();
}
- StringBuilder builder = new StringBuilder();
- String trimed = data.trim();
- int length = trimed.length();
- for (int i = 0; i < length; i++) {
- char ch = trimed.charAt(i);
- if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
- builder.append(ch);
+ final StringBuilder builder = new StringBuilder();
+ final String trimed = data.trim();
+ final String formattedNumber;
+ if (type == Phone.TYPE_PAGER) {
+ formattedNumber = trimed;
+ } else {
+ final int length = trimed.length();
+ for (int i = 0; i < length; i++) {
+ char ch = trimed.charAt(i);
+ if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
+ builder.append(ch);
+ }
}
- }
-
- PhoneData phoneData = new PhoneData(type,
- PhoneNumberUtils.formatNumber(builder.toString()),
- label, isPrimary);
+ // Use NANP in default when there's no information about locale.
+ final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ?
+ PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
+ formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
+ }
+ PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
mPhoneList.add(phoneData);
}
@@ -666,24 +500,122 @@ public class ContactStruct {
private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
if (mPostalList == null) {
- mPostalList = new ArrayList<PostalData>();
+ mPostalList = new ArrayList<PostalData>(0);
}
mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
}
- private void addOrganization(int type, final String companyName,
- final String positionName, boolean isPrimary) {
+ /**
+ * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+ * {@link #handleTitleValue(String)}.
+ */
+ private void addNewOrganization(int type, final String companyName,
+ final String departmentName,
+ final String titleName, boolean isPrimary) {
if (mOrganizationList == null) {
mOrganizationList = new ArrayList<OrganizationData>();
}
- mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary));
+ mOrganizationList.add(new OrganizationData(type, companyName,
+ departmentName, titleName, isPrimary));
}
-
- private void addIm(int type, String data, String label, boolean isPrimary) {
+
+ private static final List<String> sEmptyList =
+ Collections.unmodifiableList(new ArrayList<String>(0));
+
+ /**
+ * Set "ORG" related values to the appropriate data. If there's more than one
+ * {@link OrganizationData} objects, this input data are attached to the last one which
+ * does not have valid values (not including empty but only null). If there's no
+ * {@link OrganizationData} object, a new {@link OrganizationData} is created,
+ * whose title is set to null.
+ */
+ private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
+ if (orgList == null) {
+ orgList = sEmptyList;
+ }
+ final String companyName;
+ final String departmentName;
+ final int size = orgList.size();
+ switch (size) {
+ case 0: {
+ companyName = "";
+ departmentName = null;
+ break;
+ }
+ case 1: {
+ companyName = orgList.get(0);
+ departmentName = null;
+ break;
+ }
+ default: { // More than 1.
+ companyName = orgList.get(0);
+ // We're not sure which is the correct string for department.
+ // In order to keep all the data, concatinate the rest of elements.
+ StringBuilder builder = new StringBuilder();
+ for (int i = 1; i < size; i++) {
+ if (i > 1) {
+ builder.append(' ');
+ }
+ builder.append(orgList.get(i));
+ }
+ departmentName = builder.toString();
+ }
+ }
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" title which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
+ // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
+ if (organizationData.companyName == null &&
+ organizationData.departmentName == null) {
+ // Probably the "TITLE" property comes before the "ORG" property via
+ // handleTitleLine().
+ organizationData.companyName = companyName;
+ organizationData.departmentName = departmentName;
+ organizationData.isPrimary = isPrimary;
+ return;
+ }
+ }
+ // No OrganizatioData is available. Create another one, with "null" title, which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ }
+
+ /**
+ * Set "title" value to the appropriate data. If there's more than one
+ * OrganizationData objects, this input is attached to the last one which does not
+ * have valid title value (not including empty but only null). If there's no
+ * OrganizationData object, a new OrganizationData is created, whose company name is
+ * set to null.
+ */
+ private void handleTitleValue(final String title) {
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ if (organizationData.titleName == null) {
+ organizationData.titleName = title;
+ return;
+ }
+ }
+ // No Organization is available. Create another one, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ }
+
+ private void addIm(int protocol, String customProtocol, int type,
+ String propValue, boolean isPrimary) {
if (mImList == null) {
mImList = new ArrayList<ImData>();
}
- mImList.add(new ImData(type, data, label, isPrimary));
+ mImList.add(new ImData(protocol, customProtocol, type, propValue, isPrimary));
}
private void addNote(final String note) {
@@ -693,43 +625,14 @@ public class ContactStruct {
mNoteList.add(note);
}
- private void addPhotoBytes(String formatName, byte[] photoBytes) {
+ private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
if (mPhotoList == null) {
mPhotoList = new ArrayList<PhotoData>(1);
}
- final PhotoData photoData = new PhotoData(0, null, photoBytes);
+ final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
mPhotoList.add(photoData);
}
- /**
- * Set "position" value to the appropriate data. If there's more than one
- * OrganizationData objects, the value is set to the last one. If there's no
- * OrganizationData object, a new OrganizationData is created, whose company name is
- * empty.
- *
- * TODO: incomplete logic. fix this:
- *
- * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not
- * know how to handle it in general cases...
- * ----
- * TITLE:Software Engineer
- * ORG:Google
- * ----
- */
- private void setPosition(String positionValue) {
- if (mOrganizationList == null) {
- mOrganizationList = new ArrayList<OrganizationData>();
- }
- int size = mOrganizationList.size();
- if (size == 0) {
- addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER,
- "", null, false);
- size = 1;
- }
- OrganizationData lastData = mOrganizationList.get(size - 1);
- lastData.positionName = positionValue;
- }
-
@SuppressWarnings("fallthrough")
private void handleNProperty(List<String> elems) {
// Family, Given, Middle, Prefix, Suffix. (1 - 5)
@@ -742,51 +645,80 @@ public class ContactStruct {
}
switch (size) {
- // fallthrough
- case 5:
- mSuffix = elems.get(4);
- case 4:
- mPrefix = elems.get(3);
- case 3:
- mMiddleName = elems.get(2);
- case 2:
- mGivenName = elems.get(1);
- default:
- mFamilyName = elems.get(0);
+ // fallthrough
+ case 5: mSuffix = elems.get(4);
+ case 4: mPrefix = elems.get(3);
+ case 3: mMiddleName = elems.get(2);
+ case 2: mGivenName = elems.get(1);
+ default: mFamilyName = elems.get(0);
}
}
-
+
/**
- * Some Japanese mobile phones use this field for phonetic name,
- * since vCard 2.1 does not have "SORT-STRING" type.
- * Also, in some cases, the field has some ';'s in it.
- * Assume the ';' means the same meaning in N property
+ * Note: Some Japanese mobile phones use this field for phonetic name,
+ * since vCard 2.1 does not have "SORT-STRING" type.
+ * Also, in some cases, the field has some ';'s in it.
+ * Assume the ';' means the same meaning in N property
*/
@SuppressWarnings("fallthrough")
private void handlePhoneticNameFromSound(List<String> elems) {
- // Family, Given, Middle. (1-3)
- // This is not from specification but mere assumption. Some Japanese phones use this order.
+ if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
+ TextUtils.isEmpty(mPhoneticMiddleName) &&
+ TextUtils.isEmpty(mPhoneticGivenName))) {
+ // This means the other properties like "X-PHONETIC-FIRST-NAME" was already found.
+ // Ignore "SOUND;X-IRMC-N".
+ return;
+ }
+
int size;
if (elems == null || (size = elems.size()) < 1) {
return;
}
+
+ // Assume that the order is "Family, Given, Middle".
+ // This is not from specification but mere assumption. Some Japanese phones use this order.
if (size > 3) {
size = 3;
}
+ if (elems.get(0).length() > 0) {
+ boolean onlyFirstElemIsNonEmpty = true;
+ for (int i = 1; i < size; i++) {
+ if (elems.get(i).length() > 0) {
+ onlyFirstElemIsNonEmpty = false;
+ break;
+ }
+ }
+ if (onlyFirstElemIsNonEmpty) {
+ final String[] namesArray = elems.get(0).split(" ");
+ final int nameArrayLength = namesArray.length;
+ if (nameArrayLength == 3) {
+ // Assume the string is "Family Middle Given".
+ mPhoneticFamilyName = namesArray[0];
+ mPhoneticMiddleName = namesArray[1];
+ mPhoneticGivenName = namesArray[2];
+ } else if (nameArrayLength == 2) {
+ // Assume the string is "Family Given" based on the Japanese mobile
+ // phones' preference.
+ mPhoneticFamilyName = namesArray[0];
+ mPhoneticGivenName = namesArray[1];
+ } else {
+ mPhoneticFullName = elems.get(0);
+ }
+ return;
+ }
+ }
+
switch (size) {
- // fallthrough
- case 3:
- mPhoneticMiddleName = elems.get(2);
- case 2:
- mPhoneticGivenName = elems.get(1);
- default:
- mPhoneticFamilyName = elems.get(0);
+ // fallthrough
+ case 3: mPhoneticMiddleName = elems.get(2);
+ case 2: mPhoneticGivenName = elems.get(1);
+ default: mPhoneticFamilyName = elems.get(0);
}
}
- public void addProperty(Property property) {
- String propName = property.mPropertyName;
+ public void addProperty(final Property property) {
+ final String propName = property.mPropertyName;
final Map<String, Collection<String>> paramMap = property.mParameterMap;
final List<String> propValueList = property.mPropertyValueList;
byte[] propBytes = property.mPropertyBytes;
@@ -796,28 +728,37 @@ public class ContactStruct {
}
final String propValue = listToString(propValueList).trim();
- if (propName.equals("VERSION")) {
+ if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
// vCard version. Ignore this.
- } else if (propName.equals("FN")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_FN)) {
mFullName = propValue;
- } else if (propName.equals("NAME") && mFullName == null) {
+ } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) {
// Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
// actually exist in the real vCard data, does not exist.
mFullName = propValue;
- } else if (propName.equals("N")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_N)) {
handleNProperty(propValueList);
- } else if (propName.equals("SORT-STRING")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
mPhoneticFullName = propValue;
- } else if (propName.equals("NICKNAME") || propName.equals("X-NICKNAME")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
+ propName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) {
addNickName(propValue);
- } else if (propName.equals("SOUND")) {
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) {
- handlePhoneticNameFromSound(propValueList);
+ } else if (propName.equals(VCardConstants.PROPERTY_SOUND)) {
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ if (typeCollection != null
+ && typeCollection.contains(VCardConstants.PARAM_TYPE_X_IRMC_N)) {
+ // As of 2009-10-08, Parser side does not split a property value into separated
+ // values using ';' (in other words, propValueList.size() == 1),
+ // which is correct behavior from the view of vCard 2.1.
+ // But we want it to be separated, so do the separation here.
+ final List<String> phoneticNameList =
+ VCardUtils.constructListFromValue(propValue,
+ VCardConfig.isV30(mVCardType));
+ handlePhoneticNameFromSound(phoneticNameList);
} else {
// Ignore this field since Android cannot understand what it is.
}
- } else if (propName.equals("ADR")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_ADR)) {
boolean valuesAreAllEmpty = true;
for (String value : propValueList) {
if (value.length() > 0) {
@@ -832,27 +773,25 @@ public class ContactStruct {
int type = -1;
String label = "";
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) {
- // Only first "PREF" is considered.
- mPrefIsSet_Address = true;
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
type = StructuredPostal.TYPE_HOME;
label = "";
- } else if (typeString.equals(Constants.ATTR_TYPE_WORK) ||
- typeString.equalsIgnoreCase("COMPANY")) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK) ||
+ typeString.equalsIgnoreCase(VCardConstants.PARAM_EXTRA_TYPE_COMPANY)) {
// "COMPANY" seems emitted by Windows Mobile, which is not
// specifically supported by vCard 2.1. We assume this is same
// as "WORK".
type = StructuredPostal.TYPE_WORK;
label = "";
- } else if (typeString.equals("PARCEL") ||
- typeString.equals("DOM") ||
- typeString.equals("INTL")) {
+ } else if (typeString.equals(VCardConstants.PARAM_ADR_TYPE_PARCEL) ||
+ typeString.equals(VCardConstants.PARAM_ADR_TYPE_DOM) ||
+ typeString.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) {
// We do not have any appropriate way to store this information.
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -871,23 +810,21 @@ public class ContactStruct {
}
addPostal(type, propValueList, label, isPrimary);
- } else if (propName.equals("EMAIL")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_EMAIL)) {
int type = -1;
String label = null;
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) {
- // Only first "PREF" is considered.
- mPrefIsSet_Email = true;
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
type = Email.TYPE_HOME;
- } else if (typeString.equals(Constants.ATTR_TYPE_WORK)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK)) {
type = Email.TYPE_WORK;
- } else if (typeString.equals(Constants.ATTR_TYPE_CELL)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_CELL)) {
type = Email.TYPE_MOBILE;
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -905,50 +842,48 @@ public class ContactStruct {
type = Email.TYPE_OTHER;
}
addEmail(type, propValue, label, isPrimary);
- } else if (propName.equals("ORG")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_ORG)) {
// vCard specification does not specify other types.
- int type = Organization.TYPE_WORK;
+ final int type = Organization.TYPE_WORK;
boolean isPrimary = false;
-
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) {
- // vCard specification officially does not have PREF in ORG.
- // This is just for safety.
- mPrefIsSet_Organization = true;
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
}
}
}
-
- StringBuilder builder = new StringBuilder();
- for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) {
- builder.append(iter.next());
- if (iter.hasNext()) {
- builder.append(' ');
- }
- }
- addOrganization(type, builder.toString(), "", isPrimary);
- } else if (propName.equals("TITLE")) {
- setPosition(propValue);
- } else if (propName.equals("ROLE")) {
- setPosition(propValue);
- } else if (propName.equals("PHOTO") || propName.equals("LOGO")) {
- String formatName = null;
- Collection<String> typeCollection = paramMap.get("TYPE");
- if (typeCollection != null) {
- formatName = typeCollection.iterator().next();
- }
+ handleOrgValue(type, propValueList, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
+ handleTitleValue(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
+ // This conflicts with TITLE. Ignore for now...
+ // handleTitleValue(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_PHOTO) ||
+ propName.equals(VCardConstants.PROPERTY_LOGO)) {
Collection<String> paramMapValue = paramMap.get("VALUE");
if (paramMapValue != null && paramMapValue.contains("URL")) {
// Currently we do not have appropriate example for testing this case.
} else {
- addPhotoBytes(formatName, propBytes);
+ final Collection<String> typeCollection = paramMap.get("TYPE");
+ String formatName = null;
+ boolean isPrimary = false;
+ if (typeCollection != null) {
+ for (String typeValue : typeCollection) {
+ if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) {
+ isPrimary = true;
+ } else if (formatName == null){
+ formatName = typeValue;
+ }
+ }
+ }
+ addPhotoBytes(formatName, propBytes, isPrimary);
}
- } else if (propName.equals("TEL")) {
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
+ } else if (propName.equals(VCardConstants.PROPERTY_TEL)) {
+ final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ final Object typeObject =
+ VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
final int type;
final String label;
if (typeObject instanceof Integer) {
@@ -960,63 +895,65 @@ public class ContactStruct {
}
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
}
addPhone(type, propValue, label, isPrimary);
- } else if (propName.equals(Constants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
// The phone number available via Skype.
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- // XXX: should use TYPE_CUSTOM + the label "Skype"? (which may need localization)
- int type = Phone.TYPE_OTHER;
- final String label = null;
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ final int type = Phone.TYPE_OTHER;
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
}
- addPhone(type, propValue, label, isPrimary);
- } else if (sImMap.containsKey(propName)){
- int type = sImMap.get(propName);
+ addPhone(type, propValue, null, isPrimary);
+ } else if (sImMap.containsKey(propName)) {
+ final int protocol = sImMap.get(propName);
boolean isPrimary = false;
- final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ int type = -1;
+ final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_HOME)) {
- type = Phone.TYPE_HOME;
- } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_WORK)) {
- type = Phone.TYPE_WORK;
+ } else if (type < 0) {
+ if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) {
+ type = Im.TYPE_HOME;
+ } else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) {
+ type = Im.TYPE_WORK;
+ }
}
}
}
if (type < 0) {
type = Phone.TYPE_HOME;
}
- addIm(type, propValue, null, isPrimary);
- } else if (propName.equals("NOTE")) {
+ addIm(protocol, null, type, propValue, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_NOTE)) {
addNote(propValue);
- } else if (propName.equals("URL")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_URL)) {
if (mWebsiteList == null) {
mWebsiteList = new ArrayList<String>(1);
}
mWebsiteList.add(propValue);
- } else if (propName.equals("X-PHONETIC-FIRST-NAME")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
+ mBirthday = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
mPhoneticGivenName = propValue;
- } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
mPhoneticMiddleName = propValue;
- } else if (propName.equals("X-PHONETIC-LAST-NAME")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) {
mPhoneticFamilyName = propValue;
- } else if (propName.equals("BDAY")) {
- mBirthday = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
+ final List<String> customPropertyList =
+ VCardUtils.constructListFromValue(propValue,
+ VCardConfig.isV30(mVCardType));
+ handleAndroidCustomProperty(customPropertyList);
/*} else if (propName.equals("REV")) {
// Revision of this VCard entry. I think we can ignore this.
} else if (propName.equals("UID")) {
@@ -1044,43 +981,23 @@ public class ContactStruct {
}
}
+ private void handleAndroidCustomProperty(final List<String> customPropertyList) {
+ if (mAndroidCustomPropertyList == null) {
+ mAndroidCustomPropertyList = new ArrayList<List<String>>();
+ }
+ mAndroidCustomPropertyList.add(customPropertyList);
+ }
+
/**
* Construct the display name. The constructed data must not be null.
*/
private void constructDisplayName() {
- if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
- StringBuilder builder = new StringBuilder();
- List<String> nameList;
- switch (VCardConfig.getNameOrderType(mVCardType)) {
- case VCardConfig.NAME_ORDER_JAPANESE:
- if (VCardUtils.containsOnlyPrintableAscii(mFamilyName) &&
- VCardUtils.containsOnlyPrintableAscii(mGivenName)) {
- nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
- } else {
- nameList = Arrays.asList(mPrefix, mFamilyName, mMiddleName, mGivenName, mSuffix);
- }
- break;
- case VCardConfig.NAME_ORDER_EUROPE:
- nameList = Arrays.asList(mPrefix, mMiddleName, mGivenName, mFamilyName, mSuffix);
- break;
- default:
- nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
- break;
- }
- boolean first = true;
- for (String namePart : nameList) {
- if (!TextUtils.isEmpty(namePart)) {
- if (first) {
- first = false;
- } else {
- builder.append(' ');
- }
- builder.append(namePart);
- }
- }
- mDisplayName = builder.toString();
- } else if (!TextUtils.isEmpty(mFullName)) {
+ // FullName (created via "FN" or "NAME" field) is prefered.
+ if (!TextUtils.isEmpty(mFullName)) {
mDisplayName = mFullName;
+ } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+ mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
+ mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
} else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
TextUtils.isEmpty(mPhoneticGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
@@ -1097,36 +1014,17 @@ public class ContactStruct {
mDisplayName = "";
}
}
-
+
/**
* Consolidate several fielsds (like mName) using name candidates,
*/
public void consolidateFields() {
constructDisplayName();
-
+
if (mPhoneticFullName != null) {
mPhoneticFullName = mPhoneticFullName.trim();
}
-
- // If there is no "PREF", we choose the first entries as primary.
- if (!mPrefIsSet_Phone && mPhoneList != null && mPhoneList.size() > 0) {
- mPhoneList.get(0).isPrimary = true;
- }
-
- if (!mPrefIsSet_Address && mPostalList != null && mPostalList.size() > 0) {
- mPostalList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Email && mEmailList != null && mEmailList.size() > 0) {
- mEmailList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) {
- mOrganizationList.get(0).isPrimary = true;
- }
}
-
- // From GoogleSource.java in Contacts app.
- private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
- private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
public void pushIntoContentResolver(ContentResolver resolver) {
ArrayList<ContentProviderOperation> operationList =
@@ -1139,7 +1037,6 @@ public class ContactStruct {
builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
// Assume that caller side creates this group if it does not exist.
- // TODO: refactor this code along with the change in GoogleSource.java
if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
Groups.SOURCE_ID },
@@ -1161,7 +1058,7 @@ public class ContactStruct {
}
operationList.add(builder.build());
- {
+ if (!nameFieldsAreEmpty()) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
@@ -1172,31 +1069,31 @@ public class ContactStruct {
builder.withValue(StructuredName.PREFIX, mPrefix);
builder.withValue(StructuredName.SUFFIX, mSuffix);
- builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
- builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
- builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+ if (!(TextUtils.isEmpty(mPhoneticGivenName)
+ && TextUtils.isEmpty(mPhoneticFamilyName)
+ && TextUtils.isEmpty(mPhoneticMiddleName))) {
+ builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
+ builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
+ builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+ } else if (!TextUtils.isEmpty(mPhoneticFullName)) {
+ builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticFullName);
+ }
builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
operationList.add(builder.build());
}
if (mNickNameList != null && mNickNameList.size() > 0) {
- boolean first = true;
for (String nickName : mNickNameList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
-
builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
builder.withValue(Nickname.NAME, nickName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
- }
operationList.add(builder.build());
}
}
-
+
if (mPhoneList != null) {
for (PhoneData phoneData : mPhoneList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1209,30 +1106,34 @@ public class ContactStruct {
}
builder.withValue(Phone.NUMBER, phoneData.data);
if (phoneData.isPrimary) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ builder.withValue(Phone.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mOrganizationList != null) {
- boolean first = true;
for (OrganizationData organizationData : mOrganizationList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
-
- // Currently, we do not use TYPE_CUSTOM.
builder.withValue(Organization.TYPE, organizationData.type);
- builder.withValue(Organization.COMPANY, organizationData.companyName);
- builder.withValue(Organization.TITLE, organizationData.positionName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ if (organizationData.companyName != null) {
+ builder.withValue(Organization.COMPANY, organizationData.companyName);
+ }
+ if (organizationData.departmentName != null) {
+ builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
+ }
+ if (organizationData.titleName != null) {
+ builder.withValue(Organization.TITLE, organizationData.titleName);
+ }
+ if (organizationData.isPrimary) {
+ builder.withValue(Organization.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mEmailList != null) {
for (EmailData emailData : mEmailList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1265,12 +1166,11 @@ public class ContactStruct {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
-
builder.withValue(Im.TYPE, imData.type);
- if (imData.type == Im.TYPE_CUSTOM) {
- builder.withValue(Im.LABEL, imData.label);
+ builder.withValue(Im.PROTOCOL, imData.protocol);
+ if (imData.protocol == Im.PROTOCOL_CUSTOM) {
+ builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
}
- builder.withValue(Im.DATA, imData.data);
if (imData.isPrimary) {
builder.withValue(Data.IS_PRIMARY, 1);
}
@@ -1282,22 +1182,19 @@ public class ContactStruct {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
-
builder.withValue(Note.NOTE, note);
operationList.add(builder.build());
}
}
-
+
if (mPhotoList != null) {
- boolean first = true;
for (PhotoData photoData : mPhotoList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
builder.withValue(Photo.PHOTO, photoData.photoBytes);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
+ if (photoData.isPrimary) {
+ builder.withValue(Photo.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
@@ -1310,12 +1207,12 @@ public class ContactStruct {
builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
builder.withValue(Website.URL, website);
// There's no information about the type of URL in vCard.
- // We use TYPE_HOME for safety.
- builder.withValue(Website.TYPE, Website.TYPE_HOME);
+ // We use TYPE_HOMEPAGE for safety.
+ builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
operationList.add(builder.build());
}
}
-
+
if (!TextUtils.isEmpty(mBirthday)) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
@@ -1325,6 +1222,36 @@ public class ContactStruct {
operationList.add(builder.build());
}
+ if (mAndroidCustomPropertyList != null) {
+ for (List<String> customPropertyList : mAndroidCustomPropertyList) {
+ int size = customPropertyList.size();
+ if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
+ continue;
+ } else if (size > VCardConstants.MAX_DATA_COLUMN + 1) {
+ size = VCardConstants.MAX_DATA_COLUMN + 1;
+ customPropertyList =
+ customPropertyList.subList(0, VCardConstants.MAX_DATA_COLUMN + 2);
+ }
+
+ int i = 0;
+ for (final String customPropertyValue : customPropertyList) {
+ if (i == 0) {
+ final String mimeType = customPropertyValue;
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, mimeType);
+ } else { // 1 <= i && i <= MAX_DATA_COLUMNS
+ if (!TextUtils.isEmpty(customPropertyValue)) {
+ builder.withValue("data" + i, customPropertyValue);
+ }
+ }
+
+ i++;
+ }
+ operationList.add(builder.build());
+ }
+ }
+
if (myGroupsId != null) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
@@ -1342,6 +1269,28 @@ public class ContactStruct {
}
}
+ public static VCardEntry buildFromResolver(ContentResolver resolver) {
+ return buildFromResolver(resolver, Contacts.CONTENT_URI);
+ }
+
+ public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) {
+
+ return null;
+ }
+
+ private boolean nameFieldsAreEmpty() {
+ return (TextUtils.isEmpty(mFamilyName)
+ && TextUtils.isEmpty(mMiddleName)
+ && TextUtils.isEmpty(mGivenName)
+ && TextUtils.isEmpty(mPrefix)
+ && TextUtils.isEmpty(mSuffix)
+ && TextUtils.isEmpty(mFullName)
+ && TextUtils.isEmpty(mPhoneticFamilyName)
+ && TextUtils.isEmpty(mPhoneticMiddleName)
+ && TextUtils.isEmpty(mPhoneticGivenName)
+ && TextUtils.isEmpty(mPhoneticFullName));
+ }
+
public boolean isIgnorable() {
return getDisplayName().length() == 0;
}
@@ -1364,4 +1313,99 @@ public class ContactStruct {
return "";
}
}
+
+ // All getter methods should be used carefully, since they may change
+ // in the future as of 2009-10-05, on which I cannot be sure this structure
+ // is completely consolidated.
+ //
+ // Also note that these getter methods should be used only after
+ // all properties being pushed into this object. If not, incorrect
+ // value will "be stored in the local cache and" be returned to you.
+
+ public String getFamilyName() {
+ return mFamilyName;
+ }
+
+ public String getGivenName() {
+ return mGivenName;
+ }
+
+ public String getMiddleName() {
+ return mMiddleName;
+ }
+
+ public String getPrefix() {
+ return mPrefix;
+ }
+
+ public String getSuffix() {
+ return mSuffix;
+ }
+
+ public String getFullName() {
+ return mFullName;
+ }
+
+ public String getPhoneticFamilyName() {
+ return mPhoneticFamilyName;
+ }
+
+ public String getPhoneticGivenName() {
+ return mPhoneticGivenName;
+ }
+
+ public String getPhoneticMiddleName() {
+ return mPhoneticMiddleName;
+ }
+
+ public String getPhoneticFullName() {
+ return mPhoneticFullName;
+ }
+
+ public final List<String> getNickNameList() {
+ return mNickNameList;
+ }
+
+ public String getBirthday() {
+ return mBirthday;
+ }
+
+ public final List<String> getNotes() {
+ return mNoteList;
+ }
+
+ public final List<PhoneData> getPhoneList() {
+ return mPhoneList;
+ }
+
+ public final List<EmailData> getEmailList() {
+ return mEmailList;
+ }
+
+ public final List<PostalData> getPostalList() {
+ return mPostalList;
+ }
+
+ public final List<OrganizationData> getOrganizationList() {
+ return mOrganizationList;
+ }
+
+ public final List<ImData> getImList() {
+ return mImList;
+ }
+
+ public final List<PhotoData> getPhotoList() {
+ return mPhotoList;
+ }
+
+ public final List<String> getWebsiteList() {
+ return mWebsiteList;
+ }
+
+ public String getDisplayName() {
+ if (mDisplayName == null) {
+ constructDisplayName();
+ }
+ return mDisplayName;
+ }
}
diff --git a/core/java/android/pim/vcard/EntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java
index 3f1655d..ebbbab6 100644
--- a/core/java/android/pim/vcard/EntryCommitter.java
+++ b/core/java/android/pim/vcard/VCardEntryCommitter.java
@@ -19,28 +19,36 @@ import android.content.ContentResolver;
import android.util.Log;
/**
- * EntryHandler implementation which commits the entry to Contacts Provider
+ * <P>
+ * {@link VCardEntryHandler} implementation which commits the entry to ContentResolver.
+ * </P>
+ * <P>
+ * Note:<BR />
+ * Each vCard may contain big photo images encoded by BASE64,
+ * If we store all vCard entries in memory, OutOfMemoryError may be thrown.
+ * Thus, this class push each VCard entry into ContentResolver immediately.
+ * </P>
*/
-public class EntryCommitter implements EntryHandler {
- public static String LOG_TAG = "vcard.EntryComitter";
+public class VCardEntryCommitter implements VCardEntryHandler {
+ public static String LOG_TAG = "VCardEntryComitter";
- private ContentResolver mContentResolver;
+ private final ContentResolver mContentResolver;
private long mTimeToCommit;
-
- public EntryCommitter(ContentResolver resolver) {
+
+ public VCardEntryCommitter(ContentResolver resolver) {
mContentResolver = resolver;
}
- public void onParsingStart() {
+ public void onStart() {
}
-
- public void onParsingEnd() {
+
+ public void onEnd() {
if (VCardConfig.showPerformanceLog()) {
Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
}
}
- public void onEntryCreated(final ContactStruct contactStruct) {
+ public void onEntryCreated(final VCardEntry contactStruct) {
long start = System.currentTimeMillis();
contactStruct.pushIntoContentResolver(mContentResolver);
mTimeToCommit += System.currentTimeMillis() - start;
diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
index d2026d0..290ca2b 100644
--- a/core/java/android/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/pim/vcard/VCardEntryConstructor.java
@@ -30,123 +30,105 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-/**
- * VBuilder for VCard. VCard may contain big photo images encoded by BASE64,
- * If we store all VNode entries in memory like VDataBuilder.java,
- * OutOfMemoryError may be thrown. Thus, this class push each VCard entry into
- * ContentResolver immediately.
- */
-public class VCardDataBuilder implements VCardBuilder {
- static private String LOG_TAG = "VCardDataBuilder";
-
+public class VCardEntryConstructor implements VCardInterpreter {
+ private static String LOG_TAG = "VCardEntryConstructor";
+
/**
* If there's no other information available, this class uses this charset for encoding
- * byte arrays.
+ * byte arrays to String.
*/
- static public String TARGET_CHARSET = "UTF-8";
-
- private ContactStruct.Property mCurrentProperty = new ContactStruct.Property();
- private ContactStruct mCurrentContactStruct;
+ /* package */ static final String DEFAULT_CHARSET_FOR_DECODED_BYTES = "UTF-8";
+
+ private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
+ private VCardEntry mCurrentContactStruct;
private String mParamType;
/**
- * The charset using which VParser parses the text.
+ * The charset using which {@link VCardInterpreter} parses the text.
*/
- private String mSourceCharset;
-
+ private String mInputCharset;
+
/**
* The charset with which byte array is encoded to String.
*/
- private String mTargetCharset;
- private boolean mStrictLineBreakParsing;
-
+ final private String mCharsetForDecodedBytes;
+ final private boolean mStrictLineBreakParsing;
final private int mVCardType;
final private Account mAccount;
- // Just for testing.
+ /** For measuring performance. */
private long mTimePushIntoContentResolver;
-
- private List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
-
- public VCardDataBuilder() {
- this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC, null);
+
+ final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
+
+ public VCardEntryConstructor() {
+ this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
}
- /**
- * @hide
- */
- public VCardDataBuilder(int vcardType) {
+ public VCardEntryConstructor(final int vcardType) {
this(null, null, false, vcardType, null);
}
- /**
- * @hide
- */
- public VCardDataBuilder(String charset,
- boolean strictLineBreakParsing, int vcardType, Account account) {
+ public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing,
+ final int vcardType, final Account account) {
this(null, charset, strictLineBreakParsing, vcardType, account);
}
-
- /**
- * @hide
- */
- public VCardDataBuilder(String sourceCharset,
- String targetCharset,
- boolean strictLineBreakParsing,
- int vcardType,
- Account account) {
- if (sourceCharset != null) {
- mSourceCharset = sourceCharset;
+
+ public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes,
+ final boolean strictLineBreakParsing, final int vcardType,
+ final Account account) {
+ if (inputCharset != null) {
+ mInputCharset = inputCharset;
} else {
- mSourceCharset = VCardConfig.DEFAULT_CHARSET;
+ mInputCharset = VCardConfig.DEFAULT_CHARSET;
}
- if (targetCharset != null) {
- mTargetCharset = targetCharset;
+ if (charsetForDetodedBytes != null) {
+ mCharsetForDecodedBytes = charsetForDetodedBytes;
} else {
- mTargetCharset = TARGET_CHARSET;
+ mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
}
mStrictLineBreakParsing = strictLineBreakParsing;
mVCardType = vcardType;
mAccount = account;
}
-
- public void addEntryHandler(EntryHandler entryHandler) {
+
+ public void addEntryHandler(VCardEntryHandler entryHandler) {
mEntryHandlers.add(entryHandler);
}
public void start() {
- for (EntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onParsingStart();
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onStart();
}
}
public void end() {
- for (EntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onParsingEnd();
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onEnd();
}
}
/**
+ * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
+ */
+ public void clear() {
+ mCurrentContactStruct = null;
+ mCurrentProperty = new VCardEntry.Property();
+ }
+
+ /**
* Assume that VCard is not nested. In other words, this code does not accept
*/
- public void startRecord(String type) {
- // TODO: add the method clear() instead of using null for reducing GC?
+ public void startEntry() {
if (mCurrentContactStruct != null) {
- // This means startRecord() is called inside startRecord() - endRecord() block.
- // TODO: should throw some Exception
Log.e(LOG_TAG, "Nested VCard code is not supported now.");
}
- if (!type.equalsIgnoreCase("VCARD")) {
- // TODO: add test case for this
- Log.e(LOG_TAG, "This is not VCARD!");
- }
-
- mCurrentContactStruct = new ContactStruct(mVCardType, mAccount);
+ mCurrentContactStruct = new VCardEntry(mVCardType, mAccount);
}
- public void endRecord() {
+ public void endEntry() {
mCurrentContactStruct.consolidateFields();
- for (EntryHandler entryHandler : mEntryHandlers) {
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEntryCreated(mCurrentContactStruct);
}
mCurrentContactStruct = null;
@@ -165,9 +147,8 @@ public class VCardDataBuilder implements VCardBuilder {
}
public void propertyGroup(String group) {
- // ContactStruct does not support Group.
}
-
+
public void propertyParamType(String type) {
if (mParamType != null) {
Log.e(LOG_TAG, "propertyParamType() is called more than once " +
@@ -184,26 +165,26 @@ public class VCardDataBuilder implements VCardBuilder {
mCurrentProperty.addParameter(mParamType, value);
mParamType = null;
}
-
- private String encodeString(String originalString, String targetCharset) {
- if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
+
+ private String encodeString(String originalString, String charsetForDecodedBytes) {
+ if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) {
return originalString;
}
- Charset charset = Charset.forName(mSourceCharset);
+ Charset charset = Charset.forName(mInputCharset);
ByteBuffer byteBuffer = charset.encode(originalString);
// byteBuffer.array() "may" return byte array which is larger than
// byteBuffer.remaining(). Here, we keep on the safe side.
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
- return new String(bytes, targetCharset);
+ return new String(bytes, charsetForDecodedBytes);
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
return null;
}
}
-
- private String handleOneValue(String value, String targetCharset, String encoding) {
+
+ private String handleOneValue(String value, String charsetForDecodedBytes, String encoding) {
if (encoding != null) {
if (encoding.equals("BASE64") || encoding.equals("B")) {
mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes()));
@@ -269,9 +250,9 @@ public class VCardDataBuilder implements VCardBuilder {
}
byte[] bytes;
try {
- bytes = builder.toString().getBytes(mSourceCharset);
+ bytes = builder.toString().getBytes(mInputCharset);
} catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
+ Log.e(LOG_TAG, "Failed to encode: charset=" + mInputCharset);
bytes = builder.toString().getBytes();
}
@@ -283,38 +264,37 @@ public class VCardDataBuilder implements VCardBuilder {
}
try {
- return new String(bytes, targetCharset);
+ return new String(bytes, charsetForDecodedBytes);
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
return new String(bytes);
}
}
// Unknown encoding. Fall back to default.
}
- return encodeString(value, targetCharset);
+ return encodeString(value, charsetForDecodedBytes);
}
public void propertyValues(List<String> values) {
- if (values == null || values.size() == 0) {
+ if (values == null || values.isEmpty()) {
return;
}
final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
- String charset =
+ final String charset =
((charsetCollection != null) ? charsetCollection.iterator().next() : null);
- String targetCharset = CharsetUtils.nameForDefaultVendor(charset);
-
final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
- String encoding =
+ final String encoding =
((encodingCollection != null) ? encodingCollection.iterator().next() : null);
-
- if (targetCharset == null || targetCharset.length() == 0) {
- targetCharset = mTargetCharset;
+
+ String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset);
+ if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) {
+ charsetForDecodedBytes = mCharsetForDecodedBytes;
}
-
- for (String value : values) {
+
+ for (final String value : values) {
mCurrentProperty.addToPropertyValueList(
- handleOneValue(value, targetCharset, encoding));
+ handleOneValue(value, charsetForDecodedBytes, encoding));
}
}
diff --git a/core/java/android/pim/vcard/VCardEntryCounter.java b/core/java/android/pim/vcard/VCardEntryCounter.java
index f99b46c..7bab50d 100644
--- a/core/java/android/pim/vcard/VCardEntryCounter.java
+++ b/core/java/android/pim/vcard/VCardEntryCounter.java
@@ -17,29 +17,32 @@ package android.pim.vcard;
import java.util.List;
-public class VCardEntryCounter implements VCardBuilder {
+/**
+ * The class which just counts the number of vCard entries in the specified input.
+ */
+public class VCardEntryCounter implements VCardInterpreter {
private int mCount;
-
+
public int getCount() {
return mCount;
}
-
+
public void start() {
}
-
+
public void end() {
}
- public void startRecord(String type) {
+ public void startEntry() {
}
- public void endRecord() {
+ public void endEntry() {
mCount++;
}
-
+
public void startProperty() {
}
-
+
public void endProperty() {
}
@@ -57,4 +60,4 @@ public class VCardEntryCounter implements VCardBuilder {
public void propertyValues(List<String> values) {
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/pim/vcard/EntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java
index 7fb8114..83a67fe 100644
--- a/core/java/android/pim/vcard/EntryHandler.java
+++ b/core/java/android/pim/vcard/VCardEntryHandler.java
@@ -16,23 +16,23 @@
package android.pim.vcard;
/**
- * Unlike {@link VCardBuilder}, this (and {@link VCardDataBuilder}) assumes
- * "each VCard entry should be correctly parsed and passed to each EntryHandler object",
+ * The interface called by {@link VCardEntryConstructor}. Useful when you don't want to
+ * handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}.
*/
-public interface EntryHandler {
+public interface VCardEntryHandler {
/**
* Called when the parsing started.
*/
- public void onParsingStart();
+ public void onStart();
/**
* The method called when one VCard entry is successfully created
*/
- public void onEntryCreated(final ContactStruct entry);
+ public void onEntryCreated(final VCardEntry entry);
/**
* Called when the parsing ended.
* Able to be use this method for showing performance log, etc.
*/
- public void onParsingEnd();
+ public void onEnd();
}
diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java
new file mode 100644
index 0000000..b5237c0
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardInterpreter.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.pim.vcard;
+
+import java.util.List;
+
+/**
+ * <P>
+ * The interface which should be implemented by the classes which have to analyze each
+ * vCard entry more minutely than {@link VCardEntry} class analysis.
+ * </P>
+ * <P>
+ * Here, there are several terms specific to vCard (and this library).
+ * </P>
+ * <P>
+ * The term "entry" is one vCard representation in the input, which should start with "BEGIN:VCARD"
+ * and end with "END:VCARD".
+ * </P>
+ * <P>
+ * The term "property" is one line in vCard entry, which consists of "group", "property name",
+ * "parameter(param) names and values", and "property values".
+ * </P>
+ * <P>
+ * e.g. group1.propName;paramName1=paramValue1;paramName2=paramValue2;propertyValue1;propertyValue2...
+ * </P>
+ */
+public interface VCardInterpreter {
+ /**
+ * Called when vCard interpretation started.
+ */
+ void start();
+
+ /**
+ * Called when vCard interpretation finished.
+ */
+ void end();
+
+ /**
+ * Called when parsing one vCard entry started.
+ * More specifically, this method is called when "BEGIN:VCARD" is read.
+ */
+ void startEntry();
+
+ /**
+ * Called when parsing one vCard entry ended.
+ * More specifically, this method is called when "END:VCARD" is read.
+ * Note that {@link #startEntry()} may be called since
+ * vCard (especially 2.1) allows nested vCard.
+ */
+ void endEntry();
+
+ /**
+ * Called when reading one property started.
+ */
+ void startProperty();
+
+ /**
+ * Called when reading one property ended.
+ */
+ void endProperty();
+
+ /**
+ * @param group A group name. This method may be called more than once or may not be
+ * called at all, depending on how many gruoups are appended to the property.
+ */
+ void propertyGroup(String group);
+
+ /**
+ * @param name A property name like "N", "FN", "ADR", etc.
+ */
+ void propertyName(String name);
+
+ /**
+ * @param type A parameter name like "ENCODING", "CHARSET", etc.
+ */
+ void propertyParamType(String type);
+
+ /**
+ * @param value A parameter value. This method may be called without
+ * {@link #propertyParamType(String)} being called (when the vCard is vCard 2.1).
+ */
+ void propertyParamValue(String value);
+
+ /**
+ * @param values List of property values. The size of values would be 1 unless
+ * coressponding property name is "N", "ADR", or "ORG".
+ */
+ void propertyValues(List<String> values);
+}
diff --git a/core/java/android/pim/vcard/VCardBuilderCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java
index e3985b6..99f81f7 100644
--- a/core/java/android/pim/vcard/VCardBuilderCollection.java
+++ b/core/java/android/pim/vcard/VCardInterpreterCollection.java
@@ -18,81 +18,84 @@ package android.pim.vcard;
import java.util.Collection;
import java.util.List;
-public class VCardBuilderCollection implements VCardBuilder {
-
- private final Collection<VCardBuilder> mVCardBuilderCollection;
+/**
+ * The {@link VCardInterpreter} implementation which aggregates more than one
+ * {@link VCardInterpreter} objects and make a user object treat them as one
+ * {@link VCardInterpreter} object.
+ */
+public class VCardInterpreterCollection implements VCardInterpreter {
+ private final Collection<VCardInterpreter> mInterpreterCollection;
- public VCardBuilderCollection(Collection<VCardBuilder> vBuilderCollection) {
- mVCardBuilderCollection = vBuilderCollection;
+ public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
+ mInterpreterCollection = interpreterCollection;
}
-
- public Collection<VCardBuilder> getVCardBuilderBaseCollection() {
- return mVCardBuilderCollection;
+
+ public Collection<VCardInterpreter> getCollection() {
+ return mInterpreterCollection;
}
-
+
public void start() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.start();
}
}
-
+
public void end() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.end();
}
}
- public void startRecord(String type) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
- builder.startRecord(type);
+ public void startEntry() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.startEntry();
}
}
-
- public void endRecord() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
- builder.endRecord();
+
+ public void endEntry() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.endEntry();
}
}
public void startProperty() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.startProperty();
}
}
-
public void endProperty() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.endProperty();
}
}
public void propertyGroup(String group) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyGroup(group);
}
}
public void propertyName(String name) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyName(name);
}
}
public void propertyParamType(String type) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyParamType(type);
}
}
public void propertyParamValue(String value) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyParamValue(value);
}
}
public void propertyValues(List<String> values) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyValues(values);
}
}
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index b5e5049..57c52a6 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -21,63 +21,74 @@ 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;
+ }
+
/**
+ * <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=..." attribute), the string is decoded to raw bytes and encoded to
+ * (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,
- *
- * We recommend you to use VCardSourceDetector and detect which kind of source the
+ * </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>
+ *
* @param is The source to parse.
- * @param builder The VCardBuilderBase object which used to construct data. If you want to
- * include multiple VCardBuilderBase objects in this field, consider using
- * {#link VCardBuilderCollection} class.
+ * @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, VCardBuilder builder)
+ public abstract boolean parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException;
/**
+ * <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).
- *
+ * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K).
+ * </P>
+ *
* @param is The source to parse.
* @param charset Charset to be used.
* @param builder The VCardBuilderBase object.
* @return Returns true when successful. Otherwise returns false.
* @throws IOException, VCardException
*/
- public abstract boolean parse(InputStream is, String charset, VCardBuilder builder)
+ public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder)
throws IOException, VCardException;
/**
* The method variants which tells this object the operation is already canceled.
- * XXX: Is this really necessary?
- * @hide
*/
public abstract void parse(InputStream is, String charset,
- VCardBuilder builder, boolean canceled)
+ VCardInterpreter builder, boolean canceled)
throws IOException, VCardException;
/**
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 11b3888..c2928cb 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -15,11 +15,11 @@
*/
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.VCardNotSupportedException;
import android.pim.vcard.exception.VCardVersionException;
import android.util.Log;
@@ -31,13 +31,14 @@ import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Set;
/**
- * This class is used to parse vcard. Please refer to vCard Specification 2.1.
+ * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail.
*/
public class VCardParser_V21 extends VCardParser {
- private static final String LOG_TAG = "vcard.VCardParser_V21";
-
+ 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",
@@ -52,7 +53,7 @@ public class VCardParser_V21 extends VCardParser {
/** 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(
@@ -72,7 +73,7 @@ public class VCardParser_V21 extends VCardParser {
private String mPreviousLine;
/** The builder to build parsed data */
- protected VCardBuilder mBuilder = null;
+ protected VCardInterpreter mBuilder = null;
/**
* The encoding type. "Encoding" in vCard is different from "Charset".
@@ -82,7 +83,7 @@ public class VCardParser_V21 extends VCardParser {
protected final String sDefaultEncoding = "8BIT";
- // Should not directly read a line from this. Use getLine() instead.
+ // 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.
@@ -91,9 +92,10 @@ public class VCardParser_V21 extends VCardParser {
// In order to reduce warning message as much as possible, we hold the value which made Logger
// emit a warning message.
- protected HashSet<String> mWarningValueMap = new HashSet<String>();
-
- // Just for debugging
+ 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;
@@ -106,23 +108,25 @@ public class VCardParser_V21 extends VCardParser {
private long mTimeHandleMiscPropertyValue;
private long mTimeHandleQuotedPrintable;
private long mTimeHandleBase64;
-
- /**
- * Create a new VCard parser.
- */
+
public VCardParser_V21() {
- super();
+ this(null);
}
public VCardParser_V21(VCardSourceDetector detector) {
- super();
- if (detector != null && detector.getType() == VCardSourceDetector.TYPE_FOMA) {
+ this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
+ }
+
+ public VCardParser_V21(int parseType) {
+ super(parseType);
+ if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
mNestCount = 1;
}
}
-
+
/**
- * Parse the file at the given position
+ * Parses the file at the given position.
+ *
* vcard_file = [wsls] vcard [wsls]
*/
protected void parseVCardFile() throws IOException, VCardException {
@@ -146,18 +150,22 @@ public class VCardParser_V21 extends VCardParser {
}
}
- protected String getVersion() {
- return "2.1";
+ 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-")) &&
- !mWarningValueMap.contains(propertyName)) {
- mWarningValueMap.add(propertyName);
+ !mUnknownTypeMap.contains(propertyName)) {
+ mUnknownTypeMap.add(propertyName);
Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
}
return true;
@@ -194,11 +202,11 @@ public class VCardParser_V21 extends VCardParser {
}
}
}
-
+
/**
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF
- * "END" [ws] ":" [ws] "VCARD"
+ * 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;
@@ -219,7 +227,7 @@ public class VCardParser_V21 extends VCardParser {
long start;
if (mBuilder != null) {
start = System.currentTimeMillis();
- mBuilder.startRecord("VCARD");
+ mBuilder.startEntry();
mTimeReadStartRecord += System.currentTimeMillis() - start;
}
start = System.currentTimeMillis();
@@ -228,7 +236,7 @@ public class VCardParser_V21 extends VCardParser {
readEndVCard(true, false);
if (mBuilder != null) {
start = System.currentTimeMillis();
- mBuilder.endRecord();
+ mBuilder.endEntry();
mTimeReadEndRecord += System.currentTimeMillis() - start;
}
return true;
@@ -239,8 +247,7 @@ public class VCardParser_V21 extends VCardParser {
* @throws IOException
* @throws VCardException
*/
- protected boolean readBeginVCard(boolean allowGarbage)
- throws IOException, VCardException {
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
String line;
do {
while (true) {
@@ -255,8 +262,9 @@ public class VCardParser_V21 extends VCardParser {
int length = strArray.length;
// Though vCard 2.1/3.0 specification does not allow lower cases,
- // some data may have them, so we allow it (Actually, previous code
- // had explicitly allowed "BEGIN:vCard" though there's no example).
+ // 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")) {
@@ -279,7 +287,7 @@ public class VCardParser_V21 extends VCardParser {
/**
* 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.
@@ -322,7 +330,6 @@ public class VCardParser_V21 extends VCardParser {
* / item
*/
protected void parseItems() throws IOException, VCardException {
- /* items *CRLF item / item */
boolean ended = false;
if (mBuilder != null) {
@@ -363,12 +370,12 @@ public class VCardParser_V21 extends VCardParser {
* / [groups "."] "ADR" [params] ":" addressparts CRLF
* / [groups "."] "ORG" [params] ":" orgparts CRLF
* / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
+ * / [groups "."] "AGENT" [params] ":" vcard CRLF
*/
protected boolean parseItem() throws IOException, VCardException {
mEncoding = sDefaultEncoding;
- String line = getNonEmptyLine();
+ final String line = getNonEmptyLine();
long start = System.currentTimeMillis();
String[] propertyNameAndValue = separateLineAndHandleGroup(line);
@@ -399,9 +406,10 @@ public class VCardParser_V21 extends VCardParser {
} else {
throw new VCardException("Unknown BEGIN type: " + propertyValue);
}
- } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {
+ } else if (propertyName.equals("VERSION") &&
+ !propertyValue.equals(getVersionString())) {
throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersion());
+ propertyValue + " != " + getVersionString());
}
start = System.currentTimeMillis();
handlePropertyValue(propertyName, propertyValue);
@@ -409,23 +417,22 @@ public class VCardParser_V21 extends VCardParser {
return false;
}
- throw new VCardException("Unknown property name: \"" +
- propertyName + "\"");
+ throw new VCardException("Unknown property name: \"" + propertyName + "\"");
}
static private final int STATE_GROUP_OR_PROPNAME = 0;
static private final int STATE_PARAMS = 1;
- // vCard 3.1 specification allows double-quoted param-value, while vCard 2.1 does not.
+ // 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 length = line.length();
int state = STATE_GROUP_OR_PROPNAME;
int nameIndex = 0;
- String[] propertyNameAndValue = new String[2];
+ final String[] propertyNameAndValue = new String[2];
+ final int length = line.length();
if (length > 0 && line.charAt(0) == '#') {
throw new VCardInvalidCommentLineException();
}
@@ -433,82 +440,84 @@ public class VCardParser_V21 extends VCardParser {
for (int i = 0; i < length; i++) {
char ch = line.charAt(i);
switch (state) {
- case STATE_GROUP_OR_PROPNAME:
- 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;
- 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;
+ 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;
}
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- nameIndex = i + 1;
- state = STATE_PARAMS;
+ break;
}
- 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] = "";
+ 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;
}
- return propertyNameAndValue;
+ break;
}
- break;
- case STATE_PARAMS_IN_DQUOTE:
- if (ch == '"') {
- state = STATE_PARAMS;
+ case STATE_PARAMS_IN_DQUOTE: {
+ if (ch == '"') {
+ state = STATE_PARAMS;
+ }
+ break;
}
- 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
+ * 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);
@@ -531,19 +540,27 @@ public class VCardParser_V21 extends VCardParser {
throw new VCardException("Unknown type \"" + paramName + "\"");
}
} else {
- handleType(strArray[0]);
+ 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-")) &&
- !mWarningValueMap.contains(ptypeval)) {
- mWarningValueMap.add(ptypeval);
- Log.w(LOG_TAG, "Type unsupported by vCard 2.1: " + ptypeval);
+ !mUnknownTypeMap.contains(ptypeval)) {
+ mUnknownTypeMap.add(ptypeval);
+ Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
}
if (mBuilder != null) {
mBuilder.propertyParamType("TYPE");
@@ -554,15 +571,16 @@ public class VCardParser_V21 extends VCardParser {
/**
* pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
*/
- protected void handleValue(final String pvalueval) throws VCardException {
- if (sKnownValueSet.contains(pvalueval.toUpperCase()) ||
- pvalueval.startsWith("X-")) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("VALUE");
- mBuilder.propertyParamValue(pvalueval);
- }
- } else {
- throw new VCardException("Unknown value \"" + pvalueval + "\"");
+ 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);
}
}
@@ -583,8 +601,8 @@ public class VCardParser_V21 extends VCardParser {
}
/**
- * vCard specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
- * but some vCard contains other charset, so we allow them.
+ * 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) {
@@ -592,7 +610,7 @@ public class VCardParser_V21 extends VCardParser {
mBuilder.propertyParamValue(charsetval);
}
}
-
+
/**
* See also Section 7.1 of RFC 1521
*/
@@ -630,12 +648,12 @@ public class VCardParser_V21 extends VCardParser {
mBuilder.propertyParamValue(paramValue);
}
}
-
- protected void handlePropertyValue(String propertyName, String propertyValue) throws
- IOException, VCardException {
+
+ protected void handlePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
- long start = System.currentTimeMillis();
- String result = getQuotedPrintable(propertyValue);
+ final long start = System.currentTimeMillis();
+ final String result = getQuotedPrintable(propertyValue);
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(result);
@@ -644,11 +662,11 @@ public class VCardParser_V21 extends VCardParser {
mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
} else if (mEncoding.equalsIgnoreCase("BASE64") ||
mEncoding.equalsIgnoreCase("B")) {
- long start = System.currentTimeMillis();
+ 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 {
- String result = getBase64(propertyValue);
+ final String result = getBase64(propertyValue);
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(result);
@@ -668,7 +686,7 @@ public class VCardParser_V21 extends VCardParser {
Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\".");
}
- long start = System.currentTimeMillis();
+ final long start = System.currentTimeMillis();
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(maybeUnescapeText(propertyValue));
@@ -772,70 +790,46 @@ public class VCardParser_V21 extends VCardParser {
}
if (mBuilder != null) {
- StringBuilder builder = new StringBuilder();
- ArrayList<String> list = new ArrayList<String>();
- int length = propertyValue.length();
- for (int i = 0; i < length; i++) {
- char ch = propertyValue.charAt(i);
- if (ch == '\\' && i < length - 1) {
- char nextCh = propertyValue.charAt(i + 1);
- String unescapedString = maybeUnescapeCharacter(nextCh);
- if (unescapedString != null) {
- builder.append(unescapedString);
- i++;
- } else {
- builder.append(ch);
- }
- } else if (ch == ';') {
- list.add(builder.toString());
- builder = new StringBuilder();
- } else {
- builder.append(ch);
- }
- }
- list.add(builder.toString());
- mBuilder.propertyValues(list);
+ mBuilder.propertyValues(VCardUtils.constructListFromValue(
+ propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
}
}
-
+
/**
* 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"
- *
+ *
+ * item = ...
+ * / [groups "."] "AGENT"
+ * [params] ":" vcard CRLF
+ * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+ * items *CRLF "END" [ws] ":" [ws] "VCARD"
*/
- protected void handleAgent(String propertyValue) throws VCardException {
- throw new VCardNotSupportedException("AGENT Property is not supported now.");
- /* This is insufficient support. Also, AGENT Property is very rare.
- Ignore it for now.
-
- String[] strArray = propertyValue.split(":", 2);
- if (!(strArray.length == 2 ||
- strArray[0].trim().equalsIgnoreCase("BEGIN") &&
- strArray[1].trim().equalsIgnoreCase("VCARD"))) {
- throw new VCardException("BEGIN:VCARD != \"" + propertyValue + "\"");
+ 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.");
}
- parseItems();
- readEndVCard();
- */
+ // TODO: Support AGENT property.
}
/**
* For vCard 3.0.
*/
- protected String maybeUnescapeText(String text) {
+ 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(char ch) {
+ 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.
@@ -847,14 +841,17 @@ public class VCardParser_V21 extends VCardParser {
}
@Override
- public boolean parse(InputStream is, VCardBuilder builder)
+ 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, VCardBuilder builder)
+ 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);
@@ -882,7 +879,7 @@ public class VCardParser_V21 extends VCardParser {
}
@Override
- public void parse(InputStream is, String charset, VCardBuilder builder, boolean canceled)
+ public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled)
throws IOException, VCardException {
mCanceled = canceled;
parse(is, charset, builder);
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 384649a..4ecfe97 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -23,12 +23,12 @@ import java.util.Arrays;
import java.util.HashSet;
/**
- * This class is used to parse vcard3.0. <br>
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426)
+ * The class used to parse vCard 3.0.
+ * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
*/
public class VCardParser_V30 extends VCardParser_V21 {
- private static final String LOG_TAG = "vcard.VCardParser_V30";
-
+ private static final String LOG_TAG = "VCardParser_V30";
+
private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
@@ -46,31 +46,64 @@ public class VCardParser_V30 extends VCardParser_V21 {
private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
private String mPreviousLine;
-
+
private boolean mEmittedAgentWarning = false;
-
+
+ /**
+ * True when the caller wants the parser to be strict about the input.
+ * Currently this is only for testing.
+ */
+ private final boolean mStrictParsing;
+
+ public VCardParser_V30() {
+ super();
+ mStrictParsing = false;
+ }
+
+ /**
+ * @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(int parseMode) {
+ super(parseMode);
+ mStrictParsing = false;
+ }
+
@Override
- protected String getVersion() {
- return Constants.VERSION_V30;
+ 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-")) &&
- !mWarningValueMap.contains(propertyName)) {
- mWarningValueMap.add(propertyName);
+ !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) {
@@ -152,17 +185,17 @@ public class VCardParser_V30 extends VCardParser_V21 {
/**
- * vcard = [group "."] "BEGIN" ":" "VCARD" 1*CRLF
- * 1*(contentline)
+ * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
+ * 1 * (contentline)
* ;A vCard object MUST include the VERSION, FN and N types.
- * [group "."] "END" ":" "VCARD" 1*CRLF
+ * [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 {
@@ -189,17 +222,21 @@ public class VCardParser_V30 extends VCardParser_V21 {
}
}
}
-
+
@Override
protected void handleAnyParam(String paramName, String paramValue) {
- // vCard 3.0 accept comma-separated multiple values, but
- // current PropertyNode does not accept it.
- // For now, we do not split the values.
- //
- // TODO: fix this.
super.handleAnyParam(paramName, paramValue);
}
-
+
+ @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
*
@@ -224,7 +261,7 @@ public class VCardParser_V30 extends VCardParser_V21 {
@Override
protected void handleAgent(String propertyValue) {
- // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.0.
+ // 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
@@ -239,13 +276,13 @@ public class VCardParser_V30 extends VCardParser_V21 {
// AGENT;VALUE=uri:
// CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
//
- // This is not VCARD. Should we support this?
- // throw new VCardException("AGENT in vCard 3.0 is not supported yet.");
+ // 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;
}
- // Just ignore the line for now, since we cannot know how to handle it...
}
/**
@@ -256,7 +293,7 @@ public class VCardParser_V30 extends VCardParser_V21 {
protected String getBase64(String firstString) throws IOException, VCardException {
StringBuilder builder = new StringBuilder();
builder.append(firstString);
-
+
while (true) {
String line = getLine();
if (line == null) {
@@ -280,10 +317,14 @@ public class VCardParser_V30 extends VCardParser_V21 {
* ; \\ encodes \, \n or \N encodes newline
* ; \; encodes ;, \, encodes ,
*
- * Note: Apple escape ':' into '\:' while does not escape '\'
- */
+ * 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++) {
@@ -299,15 +340,19 @@ public class VCardParser_V30 extends VCardParser_V21 {
builder.append(ch);
}
}
- return builder.toString();
+ 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);
- }
+ }
}
}
diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
index 7e2be2b..7297c50 100644
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ b/core/java/android/pim/vcard/VCardSourceDetector.java
@@ -25,15 +25,7 @@ import java.util.Set;
* Currently this implementation is very premature.
* @hide
*/
-public class VCardSourceDetector implements VCardBuilder {
- // Should only be used in package.
- static final int TYPE_UNKNOWN = 0;
- static final int TYPE_APPLE = 1;
- static final int TYPE_JAPANESE_MOBILE_PHONE = 2; // Used in Japanese mobile phones.
- static final int TYPE_FOMA = 3; // Used in some Japanese FOMA mobile phones.
- static final int TYPE_WINDOWS_MOBILE_JP = 4;
- // TODO: Excel, etc.
-
+public class VCardSourceDetector implements VCardInterpreter {
private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
"X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
"X-ABADR", "X-ABUID"));
@@ -51,7 +43,7 @@ public class VCardSourceDetector implements VCardBuilder {
"X-SD-DESCRIPTION"));
private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
- private int mType = TYPE_UNKNOWN;
+ private int mType = VCardConfig.PARSE_TYPE_UNKNOWN;
// Some mobile phones (like FOMA) tells us the charset of the data.
private boolean mNeedParseSpecifiedCharset;
private String mSpecifiedCharset;
@@ -62,7 +54,7 @@ public class VCardSourceDetector implements VCardBuilder {
public void end() {
}
- public void startRecord(String type) {
+ public void startEntry() {
}
public void startProperty() {
@@ -72,7 +64,7 @@ public class VCardSourceDetector implements VCardBuilder {
public void endProperty() {
}
- public void endRecord() {
+ public void endEntry() {
}
public void propertyGroup(String group) {
@@ -80,21 +72,21 @@ public class VCardSourceDetector implements VCardBuilder {
public void propertyName(String name) {
if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
- mType = TYPE_FOMA;
+ mType = VCardConfig.PARSE_TYPE_FOMA;
mNeedParseSpecifiedCharset = true;
return;
}
- if (mType != TYPE_UNKNOWN) {
+ if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) {
return;
}
if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
- mType = TYPE_WINDOWS_MOBILE_JP;
+ mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP;
} else if (FOMA_SIGNS.contains(name)) {
- mType = TYPE_FOMA;
+ mType = VCardConfig.PARSE_TYPE_FOMA;
} else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
- mType = TYPE_JAPANESE_MOBILE_PHONE;
+ mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP;
} else if (APPLE_SIGNS.contains(name)) {
- mType = TYPE_APPLE;
+ mType = VCardConfig.PARSE_TYPE_APPLE;
}
}
@@ -110,7 +102,7 @@ public class VCardSourceDetector implements VCardBuilder {
}
}
- int getType() {
+ /* package */ int getEstimatedType() {
return mType;
}
@@ -124,14 +116,14 @@ public class VCardSourceDetector implements VCardBuilder {
return mSpecifiedCharset;
}
switch (mType) {
- case TYPE_WINDOWS_MOBILE_JP:
- case TYPE_FOMA:
- case TYPE_JAPANESE_MOBILE_PHONE:
- return "SHIFT_JIS";
- case TYPE_APPLE:
- return "UTF-8";
- default:
- return null;
+ case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP:
+ case VCardConfig.PARSE_TYPE_FOMA:
+ case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP:
+ return "SHIFT_JIS";
+ case VCardConfig.PARSE_TYPE_APPLE:
+ return "UTF-8";
+ default:
+ return null;
}
}
}
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index dd44288..11b112b 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -16,16 +16,19 @@
package android.pim.vcard;
import android.content.ContentProviderOperation;
-import android.content.ContentValues;
import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -33,58 +36,81 @@ import java.util.Set;
* Utilities for VCard handling codes.
*/
public class VCardUtils {
- /*
- * TODO: some of methods in this class should be placed to the more appropriate place...
- */
-
// Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
- // converted to two attribute Strings. These only contain some minor fields valid in both
+ // converted to two parameter Strings. These only contain some minor fields valid in both
// vCard and current (as of 2009-08-07) Contacts structure.
private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
- private static final Set<String> sPhoneTypesSetUnknownToContacts;
-
- private static final Map<String, Integer> sKnownPhoneTypesMap_StoI;
-
+ private static final Set<String> sPhoneTypesUnknownToContactsSet;
+ private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
+ private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
+ private static final Set<String> sMobilePhoneLabelSet;
+
static {
sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
- sKnownPhoneTypesMap_StoI = new HashMap<String, Integer>();
-
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.ATTR_TYPE_CAR);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR);
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.ATTR_TYPE_PAGER);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER);
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.ATTR_TYPE_ISDN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN);
+ sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
+
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_OTHER, Phone.TYPE_OTHER);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_CALLBACK, Phone.TYPE_CALLBACK);
- sKnownPhoneTypesMap_StoI.put(
- Constants.ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_RADIO, Phone.TYPE_RADIO);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TELEX, Phone.TYPE_TELEX);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TTY_TDD, Phone.TYPE_TTY_TDD);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_ASSISTANT, Phone.TYPE_ASSISTANT);
-
- sPhoneTypesSetUnknownToContacts = new HashSet<String>();
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MSG);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
+ Phone.TYPE_CALLBACK);
+ sKnownPhoneTypeMap_StoI.put(
+ VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
+ Phone.TYPE_TTY_TDD);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
+ Phone.TYPE_ASSISTANT);
+
+ sPhoneTypesUnknownToContactsSet = new HashSet<String>();
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
+
+ sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
+ VCardConstants.PROPERTY_X_GOOGLE_TALK);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
+
+ // \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
+ // \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
+ // \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
+ // \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
+ sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
+ "MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
+ "\uFF79\uFF72\uFF80\uFF72"));
}
-
- public static String getPhoneAttributeString(Integer type) {
+
+ public static String getPhoneTypeString(Integer type) {
return sKnownPhoneTypesMap_ItoS.get(type);
}
-
+
/**
* Returns Interger when the given types can be parsed as known type. Returns String object
* when not, which should be set to label.
*/
- public static Object getPhoneTypeFromStrings(Collection<String> types) {
+ public static Object getPhoneTypeFromStrings(Collection<String> types,
+ String number) {
+ if (number == null) {
+ number = "";
+ }
int type = -1;
String label = null;
boolean isFax = false;
@@ -92,18 +118,37 @@ public class VCardUtils {
if (types != null) {
for (String typeString : types) {
- typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+ if (typeString == null) {
+ continue;
+ }
+ typeString = typeString.toUpperCase();
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
hasPref = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_FAX)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) {
isFax = true;
} else {
if (typeString.startsWith("X-") && type < 0) {
typeString = typeString.substring(2);
}
- Integer tmp = sKnownPhoneTypesMap_StoI.get(typeString);
+ if (typeString.length() == 0) {
+ continue;
+ }
+ final Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
if (tmp != null) {
- type = tmp;
+ final int typeCandidate = tmp;
+ // TYPE_PAGER is prefered when the number contains @ surronded by
+ // a pager number and a domain name.
+ // e.g.
+ // o 1111@domain.com
+ // x @domain.com
+ // x 1111@
+ final int indexOfAt = number.indexOf("@");
+ if ((typeCandidate == Phone.TYPE_PAGER
+ && 0 < indexOfAt && indexOfAt < number.length() - 1)
+ || type < 0
+ || type == Phone.TYPE_CUSTOM) {
+ type = tmp;
+ }
} else if (type < 0) {
type = Phone.TYPE_CUSTOM;
label = typeString;
@@ -134,37 +179,54 @@ public class VCardUtils {
return type;
}
}
-
- public static boolean isValidPhoneAttribute(String phoneAttribute, int vcardType) {
- // TODO: check the following.
- // - it may violate vCard spec
- // - it may contain non-ASCII characters
- //
- // TODO: use vcardType
- return (phoneAttribute.startsWith("X-") || phoneAttribute.startsWith("x-") ||
- sPhoneTypesSetUnknownToContacts.contains(phoneAttribute));
+
+ @SuppressWarnings("deprecation")
+ public static boolean isMobilePhoneLabel(final String label) {
+ // For backward compatibility.
+ // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
+ // To support mobile type at that time, this custom label had been used.
+ return (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME.equals(label)
+ || sMobilePhoneLabelSet.contains(label));
+ }
+
+ public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
+ return sPhoneTypesUnknownToContactsSet.contains(label);
+ }
+
+ public static String getPropertyNameForIm(final int protocol) {
+ return sKnownImPropNameMap_ItoS.get(protocol);
}
-
- public static String[] sortNameElements(int vcardType,
- String familyName, String middleName, String givenName) {
- String[] list = new String[3];
- switch (VCardConfig.getNameOrderType(vcardType)) {
- case VCardConfig.NAME_ORDER_JAPANESE:
- // TODO: Should handle Ascii case?
- list[0] = familyName;
- list[1] = middleName;
- list[2] = givenName;
- break;
- case VCardConfig.NAME_ORDER_EUROPE:
- list[0] = middleName;
- list[1] = givenName;
- list[2] = familyName;
- break;
- default:
- list[0] = givenName;
- list[1] = middleName;
- list[2] = familyName;
- break;
+
+ public static String[] sortNameElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName) {
+ final String[] list = new String[3];
+ final int nameOrderType = VCardConfig.getNameOrderType(vcardType);
+ switch (nameOrderType) {
+ case VCardConfig.NAME_ORDER_JAPANESE: {
+ if (containsOnlyPrintableAscii(familyName) &&
+ containsOnlyPrintableAscii(givenName)) {
+ list[0] = givenName;
+ list[1] = middleName;
+ list[2] = familyName;
+ } else {
+ list[0] = familyName;
+ list[1] = middleName;
+ list[2] = givenName;
+ }
+ break;
+ }
+ case VCardConfig.NAME_ORDER_EUROPE: {
+ list[0] = middleName;
+ list[1] = givenName;
+ list[2] = familyName;
+ break;
+ }
+ default: {
+ list[0] = givenName;
+ list[1] = middleName;
+ list[2] = familyName;
+ break;
+ }
}
return list;
}
@@ -181,12 +243,11 @@ public class VCardUtils {
* Inserts postal data into the builder object.
*
* Note that the data structure of ContactsContract is different from that defined in vCard.
- * So some conversion may be performed in this method. See also
- * {{@link #getVCardPostalElements(ContentValues)}
+ * So some conversion may be performed in this method.
*/
public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
final ContentProviderOperation.Builder builder,
- final ContactStruct.PostalData postalData) {
+ final VCardEntry.PostalData postalData) {
builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
@@ -195,9 +256,22 @@ public class VCardUtils {
builder.withValue(StructuredPostal.LABEL, postalData.label);
}
+ final String streetString;
+ if (TextUtils.isEmpty(postalData.street)) {
+ if (TextUtils.isEmpty(postalData.extendedAddress)) {
+ streetString = null;
+ } else {
+ streetString = postalData.extendedAddress;
+ }
+ } else {
+ if (TextUtils.isEmpty(postalData.extendedAddress)) {
+ streetString = postalData.street;
+ } else {
+ streetString = postalData.street + " " + postalData.extendedAddress;
+ }
+ }
builder.withValue(StructuredPostal.POBOX, postalData.pobox);
- // Extended address is dropped since there's no relevant entry in ContactsContract.
- builder.withValue(StructuredPostal.STREET, postalData.street);
+ builder.withValue(StructuredPostal.STREET, streetString);
builder.withValue(StructuredPostal.CITY, postalData.localty);
builder.withValue(StructuredPostal.REGION, postalData.region);
builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
@@ -209,71 +283,24 @@ public class VCardUtils {
builder.withValue(Data.IS_PRIMARY, 1);
}
}
-
- /**
- * Returns String[] containing address information based on vCard spec
- * (PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name).
- * All String objects are non-null ("" is used when the relevant data is empty).
- *
- * Note that the data structure of ContactsContract is different from that defined in vCard.
- * So some conversion may be performed in this method. See also
- * {{@link #insertStructuredPostalDataUsingContactsStruct(int,
- * android.content.ContentProviderOperation.Builder,
- * android.pim.vcard.ContactStruct.PostalData)}
- */
- public static String[] getVCardPostalElements(ContentValues contentValues) {
- String[] dataArray = new String[7];
- dataArray[0] = contentValues.getAsString(StructuredPostal.POBOX);
- if (dataArray[0] == null) {
- dataArray[0] = "";
- }
- // Extended addr. There's no relevant data in ContactsContract.
- dataArray[1] = "";
- dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
- if (dataArray[2] == null) {
- dataArray[2] = "";
- }
- // Assume that localty == city
- dataArray[3] = contentValues.getAsString(StructuredPostal.CITY);
- if (dataArray[3] == null) {
- dataArray[3] = "";
- }
- String region = contentValues.getAsString(StructuredPostal.REGION);
- if (!TextUtils.isEmpty(region)) {
- dataArray[4] = region;
- } else {
- dataArray[4] = "";
- }
- dataArray[5] = contentValues.getAsString(StructuredPostal.POSTCODE);
- if (dataArray[5] == null) {
- dataArray[5] = "";
- }
- dataArray[6] = contentValues.getAsString(StructuredPostal.COUNTRY);
- if (dataArray[6] == null) {
- dataArray[6] = "";
- }
- return dataArray;
- }
-
- public static String constructNameFromElements(int nameOrderType,
- String familyName, String middleName, String givenName) {
- return constructNameFromElements(nameOrderType, familyName, middleName, givenName,
+ public static String constructNameFromElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName) {
+ return constructNameFromElements(vcardType, familyName, middleName, givenName,
null, null);
}
- public static String constructNameFromElements(int nameOrderType,
- String familyName, String middleName, String givenName,
- String prefix, String suffix) {
- StringBuilder builder = new StringBuilder();
- String[] nameList = sortNameElements(nameOrderType,
- familyName, middleName, givenName);
+ public static String constructNameFromElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName,
+ final String prefix, final String suffix) {
+ final StringBuilder builder = new StringBuilder();
+ final String[] nameList = sortNameElements(vcardType, familyName, middleName, givenName);
boolean first = true;
if (!TextUtils.isEmpty(prefix)) {
first = false;
builder.append(prefix);
}
- for (String namePart : nameList) {
+ for (final String namePart : nameList) {
if (!TextUtils.isEmpty(namePart)) {
if (first) {
first = false;
@@ -291,18 +318,52 @@ public class VCardUtils {
}
return builder.toString();
}
-
- public static boolean containsOnlyPrintableAscii(String str) {
- if (TextUtils.isEmpty(str)) {
+
+ public static List<String> constructListFromValue(final String value,
+ final boolean isV30) {
+ final List<String> list = new ArrayList<String>();
+ StringBuilder builder = new StringBuilder();
+ int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char ch = value.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ char nextCh = value.charAt(i + 1);
+ final String unescapedString =
+ (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
+ VCardParser_V21.unescapeCharacter(nextCh));
+ if (unescapedString != null) {
+ builder.append(unescapedString);
+ i++;
+ } else {
+ builder.append(ch);
+ }
+ } else if (ch == ';') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else {
+ builder.append(ch);
+ }
+ }
+ list.add(builder.toString());
+ return list;
+ }
+
+ public static boolean containsOnlyPrintableAscii(final String...values) {
+ if (values == null) {
return true;
}
-
- final int length = str.length();
- final int asciiFirst = 0x20;
- final int asciiLast = 0x126;
- for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
- int c = str.codePointAt(i);
- if (c < asciiFirst || asciiLast < c) {
+ return containsOnlyPrintableAscii(Arrays.asList(values));
+ }
+
+ public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
+ for (final String value : values) {
+ if (TextUtils.isEmpty(value)) {
+ continue;
+ }
+ if (!TextUtils.isPrintableAsciiOnly(value)) {
return false;
}
}
@@ -314,23 +375,37 @@ public class VCardUtils {
* or not, which is required by vCard 2.1.
* See the definition of "7bit" in vCard 2.1 spec for more information.
*/
- public static boolean containsOnlyNonCrLfPrintableAscii(String str) {
- if (TextUtils.isEmpty(str)) {
+ public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
+ if (values == null) {
return true;
}
+ return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values));
+ }
- final int length = str.length();
+ public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
final int asciiFirst = 0x20;
- final int asciiLast = 0x126;
- for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
- int c = str.codePointAt(i);
- if (c < asciiFirst || asciiLast < c || c == '\n' || c == '\r') {
- return false;
+ final int asciiLast = 0x7E; // included
+ for (final String value : values) {
+ if (TextUtils.isEmpty(value)) {
+ continue;
+ }
+ final int length = value.length();
+ for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+ final int c = value.codePointAt(i);
+ if (!(asciiFirst <= c && c <= asciiLast)) {
+ return false;
+ }
}
}
return true;
}
+ private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
+ new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
+
/**
* This is useful since vCard 3.0 often requires the ("X-") properties and groups
* should contain only alphabets, digits, and hyphen.
@@ -340,86 +415,79 @@ public class VCardUtils {
* such kind of input but must never output it unless the target is very specific
* to the device which is able to parse the malformed input.
*/
- public static boolean containsOnlyAlphaDigitHyphen(String str) {
- if (TextUtils.isEmpty(str)) {
+ public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
+ if (values == null) {
return true;
}
+ return containsOnlyAlphaDigitHyphen(Arrays.asList(values));
+ }
- final int lowerAlphabetFirst = 0x41; // included ('A')
- final int lowerAlphabetLast = 0x5b; // not included ('[')
- final int upperAlphabetFirst = 0x61; // included ('a')
- final int upperAlphabetLast = 0x7b; // included ('{')
- final int digitFirst = 0x30; // included ('0')
- final int digitLast = 0x39; // included ('9')
+ public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
+ final int upperAlphabetFirst = 0x41; // A
+ final int upperAlphabetAfterLast = 0x5b; // [
+ final int lowerAlphabetFirst = 0x61; // a
+ final int lowerAlphabetAfterLast = 0x7b; // {
+ final int digitFirst = 0x30; // 0
+ final int digitAfterLast = 0x3A; // :
final int hyphen = '-';
- final int length = str.length();
- for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
- int codepoint = str.codePointAt(i);
- if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetLast) ||
- (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetLast) ||
- (digitFirst <= codepoint && codepoint < digitLast) ||
+ for (final String str : values) {
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
+ final int length = str.length();
+ for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
+ int codepoint = str.codePointAt(i);
+ if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
+ (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
+ (digitFirst <= codepoint && codepoint < digitAfterLast) ||
(codepoint == hyphen))) {
- return false;
+ return false;
+ }
}
}
return true;
}
-
- // TODO: Replace wth the method in Base64 class.
- private static char PAD = '=';
- private static final char[] ENCODE64 = {
- 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
- 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
- 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
- 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
- };
-
- static public String encodeBase64(byte[] data) {
- if (data == null) {
- return "";
- }
- char[] charBuffer = new char[(data.length + 2) / 3 * 4];
- int position = 0;
- int _3byte = 0;
- for (int i=0; i<data.length-2; i+=3) {
- _3byte = ((data[i] & 0xFF) << 16) + ((data[i+1] & 0xFF) << 8) + (data[i+2] & 0xFF);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = ENCODE64[(_3byte >> 6) & 0x3F];
- charBuffer[position++] = ENCODE64[_3byte & 0x3F];
+ /**
+ * <P>
+ * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
+ * </P>
+ * <P>
+ * vCard 2.1 specifies:<BR />
+ * word = &lt;any printable 7bit us-ascii except []=:., &gt;
+ * </P>
+ */
+ public static boolean isV21Word(final String value) {
+ if (TextUtils.isEmpty(value)) {
+ return true;
}
- switch(data.length % 3) {
- case 1: // [111111][11 0000][0000 00][000000]
- _3byte = ((data[data.length-1] & 0xFF) << 16);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = PAD;
- charBuffer[position++] = PAD;
- break;
- case 2: // [111111][11 1111][1111 00][000000]
- _3byte = ((data[data.length-2] & 0xFF) << 16) + ((data[data.length-1] & 0xFF) << 8);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = ENCODE64[(_3byte >> 6) & 0x3F];
- charBuffer[position++] = PAD;
- break;
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x7E; // included
+ final int length = value.length();
+ for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+ final int c = value.codePointAt(i);
+ if (!(asciiFirst <= c && c <= asciiLast) ||
+ sUnAcceptableAsciiInV21WordSet.contains((char)c)) {
+ return false;
+ }
}
-
- return new String(charBuffer);
+ return true;
}
-
- static public String toHalfWidthString(String orgString) {
+
+ public static String toHalfWidthString(final String orgString) {
if (TextUtils.isEmpty(orgString)) {
return null;
}
- StringBuilder builder = new StringBuilder();
- int length = orgString.length();
- for (int i = 0; i < length; i++) {
+ final StringBuilder builder = new StringBuilder();
+ final int length = orgString.length();
+ for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
// All Japanese character is able to be expressed by char.
// Do not need to use String#codepPointAt().
- char ch = orgString.charAt(i);
- CharSequence halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
+ final char ch = orgString.charAt(i);
+ final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
if (halfWidthText != null) {
builder.append(halfWidthText);
} else {
@@ -428,368 +496,50 @@ public class VCardUtils {
}
return builder.toString();
}
-
- private VCardUtils() {
- }
-}
-
-/**
- * TextUtils especially for Japanese.
- * TODO: make this in android.text in the future
- */
-class JapaneseUtils {
- static private final Map<Character, String> sHalfWidthMap =
- new HashMap<Character, String>();
-
- static {
- // There's no logical mapping rule in Unicode. Sigh.
- sHalfWidthMap.put('\u3001', "\uFF64");
- sHalfWidthMap.put('\u3002', "\uFF61");
- sHalfWidthMap.put('\u300C', "\uFF62");
- sHalfWidthMap.put('\u300D', "\uFF63");
- sHalfWidthMap.put('\u301C', "~");
- sHalfWidthMap.put('\u3041', "\uFF67");
- sHalfWidthMap.put('\u3042', "\uFF71");
- sHalfWidthMap.put('\u3043', "\uFF68");
- sHalfWidthMap.put('\u3044', "\uFF72");
- sHalfWidthMap.put('\u3045', "\uFF69");
- sHalfWidthMap.put('\u3046', "\uFF73");
- sHalfWidthMap.put('\u3047', "\uFF6A");
- sHalfWidthMap.put('\u3048', "\uFF74");
- sHalfWidthMap.put('\u3049', "\uFF6B");
- sHalfWidthMap.put('\u304A', "\uFF75");
- sHalfWidthMap.put('\u304B', "\uFF76");
- sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
- sHalfWidthMap.put('\u304D', "\uFF77");
- sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
- sHalfWidthMap.put('\u304F', "\uFF78");
- sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
- sHalfWidthMap.put('\u3051', "\uFF79");
- sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
- sHalfWidthMap.put('\u3053', "\uFF7A");
- sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
- sHalfWidthMap.put('\u3055', "\uFF7B");
- sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
- sHalfWidthMap.put('\u3057', "\uFF7C");
- sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
- sHalfWidthMap.put('\u3059', "\uFF7D");
- sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
- sHalfWidthMap.put('\u305B', "\uFF7E");
- sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
- sHalfWidthMap.put('\u305D', "\uFF7F");
- sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
- sHalfWidthMap.put('\u305F', "\uFF80");
- sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
- sHalfWidthMap.put('\u3061', "\uFF81");
- sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
- sHalfWidthMap.put('\u3063', "\uFF6F");
- sHalfWidthMap.put('\u3064', "\uFF82");
- sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
- sHalfWidthMap.put('\u3066', "\uFF83");
- sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
- sHalfWidthMap.put('\u3068', "\uFF84");
- sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
- sHalfWidthMap.put('\u306A', "\uFF85");
- sHalfWidthMap.put('\u306B', "\uFF86");
- sHalfWidthMap.put('\u306C', "\uFF87");
- sHalfWidthMap.put('\u306D', "\uFF88");
- sHalfWidthMap.put('\u306E', "\uFF89");
- sHalfWidthMap.put('\u306F', "\uFF8A");
- sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
- sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
- sHalfWidthMap.put('\u3072', "\uFF8B");
- sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
- sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
- sHalfWidthMap.put('\u3075', "\uFF8C");
- sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
- sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
- sHalfWidthMap.put('\u3078', "\uFF8D");
- sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
- sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
- sHalfWidthMap.put('\u307B', "\uFF8E");
- sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
- sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
- sHalfWidthMap.put('\u307E', "\uFF8F");
- sHalfWidthMap.put('\u307F', "\uFF90");
- sHalfWidthMap.put('\u3080', "\uFF91");
- sHalfWidthMap.put('\u3081', "\uFF92");
- sHalfWidthMap.put('\u3082', "\uFF93");
- sHalfWidthMap.put('\u3083', "\uFF6C");
- sHalfWidthMap.put('\u3084', "\uFF94");
- sHalfWidthMap.put('\u3085', "\uFF6D");
- sHalfWidthMap.put('\u3086', "\uFF95");
- sHalfWidthMap.put('\u3087', "\uFF6E");
- sHalfWidthMap.put('\u3088', "\uFF96");
- sHalfWidthMap.put('\u3089', "\uFF97");
- sHalfWidthMap.put('\u308A', "\uFF98");
- sHalfWidthMap.put('\u308B', "\uFF99");
- sHalfWidthMap.put('\u308C', "\uFF9A");
- sHalfWidthMap.put('\u308D', "\uFF9B");
- sHalfWidthMap.put('\u308E', "\uFF9C");
- sHalfWidthMap.put('\u308F', "\uFF9C");
- sHalfWidthMap.put('\u3090', "\uFF72");
- sHalfWidthMap.put('\u3091', "\uFF74");
- sHalfWidthMap.put('\u3092', "\uFF66");
- sHalfWidthMap.put('\u3093', "\uFF9D");
- sHalfWidthMap.put('\u309B', "\uFF9E");
- sHalfWidthMap.put('\u309C', "\uFF9F");
- sHalfWidthMap.put('\u30A1', "\uFF67");
- sHalfWidthMap.put('\u30A2', "\uFF71");
- sHalfWidthMap.put('\u30A3', "\uFF68");
- sHalfWidthMap.put('\u30A4', "\uFF72");
- sHalfWidthMap.put('\u30A5', "\uFF69");
- sHalfWidthMap.put('\u30A6', "\uFF73");
- sHalfWidthMap.put('\u30A7', "\uFF6A");
- sHalfWidthMap.put('\u30A8', "\uFF74");
- sHalfWidthMap.put('\u30A9', "\uFF6B");
- sHalfWidthMap.put('\u30AA', "\uFF75");
- sHalfWidthMap.put('\u30AB', "\uFF76");
- sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
- sHalfWidthMap.put('\u30AD', "\uFF77");
- sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
- sHalfWidthMap.put('\u30AF', "\uFF78");
- sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
- sHalfWidthMap.put('\u30B1', "\uFF79");
- sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
- sHalfWidthMap.put('\u30B3', "\uFF7A");
- sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
- sHalfWidthMap.put('\u30B5', "\uFF7B");
- sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
- sHalfWidthMap.put('\u30B7', "\uFF7C");
- sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
- sHalfWidthMap.put('\u30B9', "\uFF7D");
- sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
- sHalfWidthMap.put('\u30BB', "\uFF7E");
- sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
- sHalfWidthMap.put('\u30BD', "\uFF7F");
- sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
- sHalfWidthMap.put('\u30BF', "\uFF80");
- sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
- sHalfWidthMap.put('\u30C1', "\uFF81");
- sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
- sHalfWidthMap.put('\u30C3', "\uFF6F");
- sHalfWidthMap.put('\u30C4', "\uFF82");
- sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
- sHalfWidthMap.put('\u30C6', "\uFF83");
- sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
- sHalfWidthMap.put('\u30C8', "\uFF84");
- sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
- sHalfWidthMap.put('\u30CA', "\uFF85");
- sHalfWidthMap.put('\u30CB', "\uFF86");
- sHalfWidthMap.put('\u30CC', "\uFF87");
- sHalfWidthMap.put('\u30CD', "\uFF88");
- sHalfWidthMap.put('\u30CE', "\uFF89");
- sHalfWidthMap.put('\u30CF', "\uFF8A");
- sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
- sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
- sHalfWidthMap.put('\u30D2', "\uFF8B");
- sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
- sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
- sHalfWidthMap.put('\u30D5', "\uFF8C");
- sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
- sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
- sHalfWidthMap.put('\u30D8', "\uFF8D");
- sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
- sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
- sHalfWidthMap.put('\u30DB', "\uFF8E");
- sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
- sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
- sHalfWidthMap.put('\u30DE', "\uFF8F");
- sHalfWidthMap.put('\u30DF', "\uFF90");
- sHalfWidthMap.put('\u30E0', "\uFF91");
- sHalfWidthMap.put('\u30E1', "\uFF92");
- sHalfWidthMap.put('\u30E2', "\uFF93");
- sHalfWidthMap.put('\u30E3', "\uFF6C");
- sHalfWidthMap.put('\u30E4', "\uFF94");
- sHalfWidthMap.put('\u30E5', "\uFF6D");
- sHalfWidthMap.put('\u30E6', "\uFF95");
- sHalfWidthMap.put('\u30E7', "\uFF6E");
- sHalfWidthMap.put('\u30E8', "\uFF96");
- sHalfWidthMap.put('\u30E9', "\uFF97");
- sHalfWidthMap.put('\u30EA', "\uFF98");
- sHalfWidthMap.put('\u30EB', "\uFF99");
- sHalfWidthMap.put('\u30EC', "\uFF9A");
- sHalfWidthMap.put('\u30ED', "\uFF9B");
- sHalfWidthMap.put('\u30EE', "\uFF9C");
- sHalfWidthMap.put('\u30EF', "\uFF9C");
- sHalfWidthMap.put('\u30F0', "\uFF72");
- sHalfWidthMap.put('\u30F1', "\uFF74");
- sHalfWidthMap.put('\u30F2', "\uFF66");
- sHalfWidthMap.put('\u30F3', "\uFF9D");
- sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
- sHalfWidthMap.put('\u30F5', "\uFF76");
- sHalfWidthMap.put('\u30F6', "\uFF79");
- sHalfWidthMap.put('\u30FB', "\uFF65");
- sHalfWidthMap.put('\u30FC', "\uFF70");
- sHalfWidthMap.put('\uFF01', "!");
- sHalfWidthMap.put('\uFF02', "\"");
- sHalfWidthMap.put('\uFF03', "#");
- sHalfWidthMap.put('\uFF04', "$");
- sHalfWidthMap.put('\uFF05', "%");
- sHalfWidthMap.put('\uFF06', "&");
- sHalfWidthMap.put('\uFF07', "'");
- sHalfWidthMap.put('\uFF08', "(");
- sHalfWidthMap.put('\uFF09', ")");
- sHalfWidthMap.put('\uFF0A', "*");
- sHalfWidthMap.put('\uFF0B', "+");
- sHalfWidthMap.put('\uFF0C', ",");
- sHalfWidthMap.put('\uFF0D', "-");
- sHalfWidthMap.put('\uFF0E', ".");
- sHalfWidthMap.put('\uFF0F', "/");
- sHalfWidthMap.put('\uFF10', "0");
- sHalfWidthMap.put('\uFF11', "1");
- sHalfWidthMap.put('\uFF12', "2");
- sHalfWidthMap.put('\uFF13', "3");
- sHalfWidthMap.put('\uFF14', "4");
- sHalfWidthMap.put('\uFF15', "5");
- sHalfWidthMap.put('\uFF16', "6");
- sHalfWidthMap.put('\uFF17', "7");
- sHalfWidthMap.put('\uFF18', "8");
- sHalfWidthMap.put('\uFF19', "9");
- sHalfWidthMap.put('\uFF1A', ":");
- sHalfWidthMap.put('\uFF1B', ";");
- sHalfWidthMap.put('\uFF1C', "<");
- sHalfWidthMap.put('\uFF1D', "=");
- sHalfWidthMap.put('\uFF1E', ">");
- sHalfWidthMap.put('\uFF1F', "?");
- sHalfWidthMap.put('\uFF20', "@");
- sHalfWidthMap.put('\uFF21', "A");
- sHalfWidthMap.put('\uFF22', "B");
- sHalfWidthMap.put('\uFF23', "C");
- sHalfWidthMap.put('\uFF24', "D");
- sHalfWidthMap.put('\uFF25', "E");
- sHalfWidthMap.put('\uFF26', "F");
- sHalfWidthMap.put('\uFF27', "G");
- sHalfWidthMap.put('\uFF28', "H");
- sHalfWidthMap.put('\uFF29', "I");
- sHalfWidthMap.put('\uFF2A', "J");
- sHalfWidthMap.put('\uFF2B', "K");
- sHalfWidthMap.put('\uFF2C', "L");
- sHalfWidthMap.put('\uFF2D', "M");
- sHalfWidthMap.put('\uFF2E', "N");
- sHalfWidthMap.put('\uFF2F', "O");
- sHalfWidthMap.put('\uFF30', "P");
- sHalfWidthMap.put('\uFF31', "Q");
- sHalfWidthMap.put('\uFF32', "R");
- sHalfWidthMap.put('\uFF33', "S");
- sHalfWidthMap.put('\uFF34', "T");
- sHalfWidthMap.put('\uFF35', "U");
- sHalfWidthMap.put('\uFF36', "V");
- sHalfWidthMap.put('\uFF37', "W");
- sHalfWidthMap.put('\uFF38', "X");
- sHalfWidthMap.put('\uFF39', "Y");
- sHalfWidthMap.put('\uFF3A', "Z");
- sHalfWidthMap.put('\uFF3B', "[");
- sHalfWidthMap.put('\uFF3C', "\\");
- sHalfWidthMap.put('\uFF3D', "]");
- sHalfWidthMap.put('\uFF3E', "^");
- sHalfWidthMap.put('\uFF3F', "_");
- sHalfWidthMap.put('\uFF41', "a");
- sHalfWidthMap.put('\uFF42', "b");
- sHalfWidthMap.put('\uFF43', "c");
- sHalfWidthMap.put('\uFF44', "d");
- sHalfWidthMap.put('\uFF45', "e");
- sHalfWidthMap.put('\uFF46', "f");
- sHalfWidthMap.put('\uFF47', "g");
- sHalfWidthMap.put('\uFF48', "h");
- sHalfWidthMap.put('\uFF49', "i");
- sHalfWidthMap.put('\uFF4A', "j");
- sHalfWidthMap.put('\uFF4B', "k");
- sHalfWidthMap.put('\uFF4C', "l");
- sHalfWidthMap.put('\uFF4D', "m");
- sHalfWidthMap.put('\uFF4E', "n");
- sHalfWidthMap.put('\uFF4F', "o");
- sHalfWidthMap.put('\uFF50', "p");
- sHalfWidthMap.put('\uFF51', "q");
- sHalfWidthMap.put('\uFF52', "r");
- sHalfWidthMap.put('\uFF53', "s");
- sHalfWidthMap.put('\uFF54', "t");
- sHalfWidthMap.put('\uFF55', "u");
- sHalfWidthMap.put('\uFF56', "v");
- sHalfWidthMap.put('\uFF57', "w");
- sHalfWidthMap.put('\uFF58', "x");
- sHalfWidthMap.put('\uFF59', "y");
- sHalfWidthMap.put('\uFF5A', "z");
- sHalfWidthMap.put('\uFF5B', "{");
- sHalfWidthMap.put('\uFF5C', "|");
- sHalfWidthMap.put('\uFF5D', "}");
- sHalfWidthMap.put('\uFF5E', "~");
- sHalfWidthMap.put('\uFF61', "\uFF61");
- sHalfWidthMap.put('\uFF62', "\uFF62");
- sHalfWidthMap.put('\uFF63', "\uFF63");
- sHalfWidthMap.put('\uFF64', "\uFF64");
- sHalfWidthMap.put('\uFF65', "\uFF65");
- sHalfWidthMap.put('\uFF66', "\uFF66");
- sHalfWidthMap.put('\uFF67', "\uFF67");
- sHalfWidthMap.put('\uFF68', "\uFF68");
- sHalfWidthMap.put('\uFF69', "\uFF69");
- sHalfWidthMap.put('\uFF6A', "\uFF6A");
- sHalfWidthMap.put('\uFF6B', "\uFF6B");
- sHalfWidthMap.put('\uFF6C', "\uFF6C");
- sHalfWidthMap.put('\uFF6D', "\uFF6D");
- sHalfWidthMap.put('\uFF6E', "\uFF6E");
- sHalfWidthMap.put('\uFF6F', "\uFF6F");
- sHalfWidthMap.put('\uFF70', "\uFF70");
- sHalfWidthMap.put('\uFF71', "\uFF71");
- sHalfWidthMap.put('\uFF72', "\uFF72");
- sHalfWidthMap.put('\uFF73', "\uFF73");
- sHalfWidthMap.put('\uFF74', "\uFF74");
- sHalfWidthMap.put('\uFF75', "\uFF75");
- sHalfWidthMap.put('\uFF76', "\uFF76");
- sHalfWidthMap.put('\uFF77', "\uFF77");
- sHalfWidthMap.put('\uFF78', "\uFF78");
- sHalfWidthMap.put('\uFF79', "\uFF79");
- sHalfWidthMap.put('\uFF7A', "\uFF7A");
- sHalfWidthMap.put('\uFF7B', "\uFF7B");
- sHalfWidthMap.put('\uFF7C', "\uFF7C");
- sHalfWidthMap.put('\uFF7D', "\uFF7D");
- sHalfWidthMap.put('\uFF7E', "\uFF7E");
- sHalfWidthMap.put('\uFF7F', "\uFF7F");
- sHalfWidthMap.put('\uFF80', "\uFF80");
- sHalfWidthMap.put('\uFF81', "\uFF81");
- sHalfWidthMap.put('\uFF82', "\uFF82");
- sHalfWidthMap.put('\uFF83', "\uFF83");
- sHalfWidthMap.put('\uFF84', "\uFF84");
- sHalfWidthMap.put('\uFF85', "\uFF85");
- sHalfWidthMap.put('\uFF86', "\uFF86");
- sHalfWidthMap.put('\uFF87', "\uFF87");
- sHalfWidthMap.put('\uFF88', "\uFF88");
- sHalfWidthMap.put('\uFF89', "\uFF89");
- sHalfWidthMap.put('\uFF8A', "\uFF8A");
- sHalfWidthMap.put('\uFF8B', "\uFF8B");
- sHalfWidthMap.put('\uFF8C', "\uFF8C");
- sHalfWidthMap.put('\uFF8D', "\uFF8D");
- sHalfWidthMap.put('\uFF8E', "\uFF8E");
- sHalfWidthMap.put('\uFF8F', "\uFF8F");
- sHalfWidthMap.put('\uFF90', "\uFF90");
- sHalfWidthMap.put('\uFF91', "\uFF91");
- sHalfWidthMap.put('\uFF92', "\uFF92");
- sHalfWidthMap.put('\uFF93', "\uFF93");
- sHalfWidthMap.put('\uFF94', "\uFF94");
- sHalfWidthMap.put('\uFF95', "\uFF95");
- sHalfWidthMap.put('\uFF96', "\uFF96");
- sHalfWidthMap.put('\uFF97', "\uFF97");
- sHalfWidthMap.put('\uFF98', "\uFF98");
- sHalfWidthMap.put('\uFF99', "\uFF99");
- sHalfWidthMap.put('\uFF9A', "\uFF9A");
- sHalfWidthMap.put('\uFF9B', "\uFF9B");
- sHalfWidthMap.put('\uFF9C', "\uFF9C");
- sHalfWidthMap.put('\uFF9D', "\uFF9D");
- sHalfWidthMap.put('\uFF9E', "\uFF9E");
- sHalfWidthMap.put('\uFF9F', "\uFF9F");
- sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
- }
/**
- * Return half-width version of that character if possible. Return null if not possible
- * @param ch input character
- * @return CharSequence object if the mapping for ch exists. Return null otherwise.
+ * Guesses the format of input image. Currently just the first few bytes are used.
+ * The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
+ * the guess failed.
+ * @param input Image as byte array.
+ * @return The image type or null when the type cannot be determined.
*/
- public static CharSequence tryGetHalfWidthText(char ch) {
- if (sHalfWidthMap.containsKey(ch)) {
- return sHalfWidthMap.get(ch);
+ public static String guessImageType(final byte[] input) {
+ if (input == null) {
+ return null;
+ }
+ if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
+ return "GIF";
+ } else if (input.length >= 4 && input[0] == (byte) 0x89
+ && input[1] == 'P' && input[2] == 'N' && input[3] == 'G') {
+ // Note: vCard 2.1 officially does not support PNG, but we may have it and
+ // using X- word like "X-PNG" may not let importers know it is PNG.
+ // So we use the String "PNG" as is...
+ return "PNG";
+ } else if (input.length >= 2 && input[0] == (byte) 0xff
+ && input[1] == (byte) 0xd8) {
+ return "JPEG";
} else {
return null;
}
}
-} \ No newline at end of file
+
+ /**
+ * @return True when all the given values are null or empty Strings.
+ */
+ public static boolean areAllEmpty(final String...values) {
+ if (values == null) {
+ return true;
+ }
+
+ for (final String value : values) {
+ if (!TextUtils.isEmpty(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private VCardUtils() {
+ }
+}
diff --git a/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java b/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java
new file mode 100644
index 0000000..e72c7df
--- /dev/null
+++ b/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java
@@ -0,0 +1,27 @@
+/*
+ * 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 android.pim.vcard.exception;
+
+public class VCardAgentNotSupportedException extends VCardNotSupportedException {
+ public VCardAgentNotSupportedException() {
+ super();
+ }
+
+ public VCardAgentNotSupportedException(String message) {
+ super(message);
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
index 84ee950..aa27627 100644
--- a/core/java/android/preference/EditTextPreference.java
+++ b/core/java/android/preference/EditTextPreference.java
@@ -28,7 +28,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.EditText;
-import android.widget.LinearLayout;
/**
* A {@link Preference} that allows for string
@@ -128,7 +127,7 @@ public class EditTextPreference extends DialogPreference {
ViewGroup container = (ViewGroup) dialogView
.findViewById(com.android.internal.R.id.edittext_container);
if (container != null) {
- container.addView(editText, ViewGroup.LayoutParams.FILL_PARENT,
+ container.addView(editText, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 08a2a9f..197d976 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -188,17 +188,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Preference);
- if (a.hasValue(com.android.internal.R.styleable.Preference_layout) ||
- a.hasValue(com.android.internal.R.styleable.Preference_widgetLayout)) {
- // This preference has a custom layout defined (not one taken from
- // the default style)
- mHasSpecifiedLayout = true;
- }
- a.recycle();
-
- a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference,
- defStyle, 0);
+ com.android.internal.R.styleable.Preference, defStyle, 0);
for (int i = a.getIndexCount(); i >= 0; i--) {
int attr = a.getIndex(i);
switch (attr) {
@@ -252,6 +242,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
}
a.recycle();
+
+ if (!getClass().getName().startsWith("android.preference")) {
+ // For subclasses not in this package, assume the worst and don't cache views
+ mHasSpecifiedLayout = true;
+ }
}
/**
@@ -332,11 +327,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* @see #setWidgetLayoutResource(int)
*/
public void setLayoutResource(int layoutResId) {
-
- if (!mHasSpecifiedLayout) {
+ if (layoutResId != mLayoutResId) {
+ // Layout changed
mHasSpecifiedLayout = true;
}
-
+
mLayoutResId = layoutResId;
}
@@ -360,6 +355,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* @see #setLayoutResource(int)
*/
public void setWidgetLayoutResource(int widgetLayoutResId) {
+ if (widgetLayoutResId != mWidgetLayoutResId) {
+ // Layout changed
+ mHasSpecifiedLayout = true;
+ }
mWidgetLayoutResId = widgetLayoutResId;
}
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 14c0054..a908ecd 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -69,7 +69,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
* count once--when the adapter is being set). We will not recycle views for
* Preference subclasses seen after the count has been returned.
*/
- private List<String> mPreferenceClassNames;
+ private ArrayList<PreferenceLayout> mPreferenceLayouts;
+
+ private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
/**
* Blocks the mPreferenceClassNames from being changed anymore.
@@ -86,14 +88,37 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
}
};
+ private static class PreferenceLayout implements Comparable<PreferenceLayout> {
+ private int resId;
+ private int widgetResId;
+ private String name;
+
+ public int compareTo(PreferenceLayout other) {
+ int compareNames = name.compareTo(other.name);
+ if (compareNames == 0) {
+ if (resId == other.resId) {
+ if (widgetResId == other.widgetResId) {
+ return 0;
+ } else {
+ return widgetResId - other.widgetResId;
+ }
+ } else {
+ return resId - other.resId;
+ }
+ } else {
+ return compareNames;
+ }
+ }
+ }
+
public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
mPreferenceGroup = preferenceGroup;
// If this group gets or loses any children, let us know
mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
-
+
mPreferenceList = new ArrayList<Preference>();
- mPreferenceClassNames = new ArrayList<String>();
-
+ mPreferenceLayouts = new ArrayList<PreferenceLayout>();
+
syncMyPreferences();
}
@@ -102,7 +127,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
if (mIsSyncing) {
return;
}
-
+
mIsSyncing = true;
}
@@ -128,7 +153,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
preferences.add(preference);
- if (!mHasReturnedViewTypeCount) {
+ if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {
addPreferenceClassName(preference);
}
@@ -143,15 +168,28 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
}
}
+ /**
+ * Creates a string that includes the preference name, layout id and widget layout id.
+ * If a particular preference type uses 2 different resources, they will be treated as
+ * different view types.
+ */
+ private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
+ PreferenceLayout pl = in != null? in : new PreferenceLayout();
+ pl.name = preference.getClass().getName();
+ pl.resId = preference.getLayoutResource();
+ pl.widgetResId = preference.getWidgetLayoutResource();
+ return pl;
+ }
+
private void addPreferenceClassName(Preference preference) {
- final String name = preference.getClass().getName();
- int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
-
+ final PreferenceLayout pl = createPreferenceLayout(preference, null);
+ int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
+
// Only insert if it doesn't exist (when it is negative).
if (insertPos < 0) {
// Convert to insert index
insertPos = insertPos * -1 - 1;
- mPreferenceClassNames.add(insertPos, name);
+ mPreferenceLayouts.add(insertPos, pl);
}
}
@@ -171,19 +209,15 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
public View getView(int position, View convertView, ViewGroup parent) {
final Preference preference = this.getItem(position);
-
- if (preference.hasSpecifiedLayout()) {
- // If the preference had specified a layout (as opposed to the
- // default), don't use convert views.
+ // Build a PreferenceLayout to compare with known ones that are cacheable.
+ mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+ // If it's not one of the cached ones, set the convertView to null so that
+ // the layout gets re-created by the Preference.
+ if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
convertView = null;
- } else {
- // TODO: better way of doing this
- final String name = preference.getClass().getName();
- if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
- convertView = null;
- }
}
-
+
return preference.getView(convertView, parent);
}
@@ -225,8 +259,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
return IGNORE_ITEM_VIEW_TYPE;
}
- final String name = preference.getClass().getName();
- int viewType = Collections.binarySearch(mPreferenceClassNames, name);
+ mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+ int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
if (viewType < 0) {
// This is a class that was seen after we returned the count, so
// don't recycle it.
@@ -242,7 +277,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
mHasReturnedViewTypeCount = true;
}
- return Math.max(1, mPreferenceClassNames.size());
+ return Math.max(1, mPreferenceLayouts.size());
}
}
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index c8b7f99..b876f05 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -108,7 +108,7 @@ public class Browser {
BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
BookmarkColumns.FAVICON, BookmarkColumns.THUMBNAIL,
- BookmarkColumns.TOUCH_ICON };
+ BookmarkColumns.TOUCH_ICON, BookmarkColumns.USER_ENTERED };
/* these indices dependent on HISTORY_PROJECTION */
public static final int HISTORY_PROJECTION_ID_INDEX = 0;
@@ -232,8 +232,8 @@ public class Browser {
* Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param url The site being visited.
- * @param real Whether this is an actual visit, and should be added to the
- * number of visits.
+ * @param real If true, this is an actual visit, and should add to the
+ * number of visits. If false, the user entered it manually.
*/
public static final void updateVisitedHistory(ContentResolver cr,
String url, boolean real) {
@@ -253,18 +253,30 @@ public class Browser {
if (real) {
map.put(BookmarkColumns.VISITS, c
.getInt(HISTORY_PROJECTION_VISITS_INDEX) + 1);
+ } else {
+ map.put(BookmarkColumns.USER_ENTERED, 1);
}
map.put(BookmarkColumns.DATE, now);
cr.update(BOOKMARKS_URI, map, "_id = " + c.getInt(0), null);
} else {
truncateHistory(cr);
ContentValues map = new ContentValues();
+ int visits;
+ int user_entered;
+ if (real) {
+ visits = 1;
+ user_entered = 0;
+ } else {
+ visits = 0;
+ user_entered = 1;
+ }
map.put(BookmarkColumns.URL, url);
- map.put(BookmarkColumns.VISITS, real ? 1 : 0);
+ map.put(BookmarkColumns.VISITS, visits);
map.put(BookmarkColumns.DATE, now);
map.put(BookmarkColumns.BOOKMARK, 0);
map.put(BookmarkColumns.TITLE, url);
map.put(BookmarkColumns.CREATED, 0);
+ map.put(BookmarkColumns.USER_ENTERED, user_entered);
cr.insert(BOOKMARKS_URI, map);
}
c.deactivate();
@@ -572,6 +584,10 @@ public class Browser {
* @hide
*/
public static final String TOUCH_ICON = "touch_icon";
+ /**
+ * @hide
+ */
+ public static final String USER_ENTERED = "user_entered";
}
public static class SearchColumns implements BaseColumns {
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f046cef..509aac5 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.accounts.Account;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -23,8 +24,14 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.EntityIterator;
+import android.content.CursorEntityIterator;
+import android.content.Entity;
+import android.content.ContentProviderClient;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.net.Uri;
+import android.os.RemoteException;
import android.pim.ICalendar;
import android.pim.RecurrenceSet;
import android.text.TextUtils;
@@ -32,18 +39,6 @@ import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Config;
import android.util.Log;
-import android.accounts.Account;
-import com.android.internal.database.ArrayListCursor;
-import com.google.android.gdata.client.AndroidGDataClient;
-import com.google.android.gdata.client.AndroidXmlParserFactory;
-import com.google.wireless.gdata.calendar.client.CalendarClient;
-import com.google.wireless.gdata.calendar.data.EventEntry;
-import com.google.wireless.gdata.calendar.data.Who;
-import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
-import com.google.wireless.gdata.data.StringUtils;
-
-import java.util.ArrayList;
-import java.util.Vector;
/**
* The Calendar provider contains all calendar events.
@@ -76,16 +71,20 @@ public final class Calendar {
Uri.parse("content://" + AUTHORITY);
/**
+ * An optional insert, update or delete URI parameter that allows the caller
+ * to specify that it is a sync adapter. The default value is false. If true
+ * the dirty flag is not automatically set and the "syncToNetwork" parameter
+ * is set to false when calling
+ * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ */
+ public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+
+ /**
* Columns from the Calendars table that other tables join into themselves.
*/
public interface CalendarsColumns
{
/**
- * A string that uniquely identifies this contact to its source
- */
- public static final String SOURCE_ID = "sourceid";
-
- /**
* The color of the calendar
* <P>Type: INTEGER (color value)</P>
*/
@@ -137,12 +136,60 @@ public final class Calendar {
* <p>Type: String (blob)</p>
*/
public static final String SYNC_STATE = "sync_state";
+
+ /**
+ * The account that was used to sync the entry to the device.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ACCOUNT = "_sync_account";
+
+ /**
+ * The type of the account that was used to sync the entry to the device.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
+
+ /**
+ * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ID = "_sync_id";
+
+ /**
+ * The last time, from the sync source's point of view, that this row has been synchronized.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_TIME = "_sync_time";
+
+ /**
+ * The version of the row, as assigned by the server.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_VERSION = "_sync_version";
+
+ /**
+ * Used in temporary provider while syncing, always NULL for rows in persistent providers.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_LOCAL_ID = "_sync_local_id";
+
+ /**
+ * Used only in persistent providers, and only during merging.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_MARK = "_sync_mark";
+
+ /**
+ * Used to indicate that local, unsynced, changes are present.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_DIRTY = "_sync_dirty";
}
/**
* Contains a list of available calendars.
*/
- public static class Calendars implements BaseColumns, SyncConstValue, CalendarsColumns
+ public static class Calendars implements BaseColumns, CalendarsColumns
{
public static final Cursor query(ContentResolver cr, String[] projection,
String where, String orderBy)
@@ -341,11 +388,11 @@ public final class Calendar {
* This field is copied here so that we can efficiently filter out
* events that are declined without having to look in the Attendees
* table.
- *
+ *
* <P>Type: INTEGER (int)</P>
*/
public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
-
+
/**
* The comments feed uri.
* <P>Type: TEXT</P>
@@ -514,13 +561,207 @@ public final class Calendar {
* <P>Type: String</P>
*/
public static final String OWNER_ACCOUNT = "ownerAccount";
+
+ /**
+ * Whether the row has been deleted. A deleted row should be ignored.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DELETED = "deleted";
+ }
+
+ /**
+ * Contains one entry per calendar event. Recurring events show up as a single entry.
+ */
+ public static final class EventsEntity implements BaseColumns, EventsColumns, CalendarsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://calendar/event_entities");
+
+ /**
+ * The name of the account instance to which this row belongs, which when paired with
+ * {@link #ACCOUNT_TYPE} identifies a specific account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_NAME = "_sync_account";
+
+ /**
+ * The type of account to which this row belongs, which when paired with
+ * {@link #ACCOUNT_NAME} identifies a specific account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_TYPE = "_sync_account_type";
+
+ public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
+ return new EntityIteratorImpl(cursor, resolver);
+ }
+
+ public static EntityIterator newEntityIterator(Cursor cursor,
+ ContentProviderClient provider) {
+ return new EntityIteratorImpl(cursor, provider);
+ }
+
+ private static class EntityIteratorImpl extends CursorEntityIterator {
+ private final ContentResolver mResolver;
+ private final ContentProviderClient mProvider;
+
+ private static final String[] REMINDERS_PROJECTION = new String[] {
+ Reminders.MINUTES,
+ Reminders.METHOD,
+ };
+ private static final int COLUMN_MINUTES = 0;
+ private static final int COLUMN_METHOD = 1;
+
+ private static final String[] ATTENDEES_PROJECTION = new String[] {
+ Attendees.ATTENDEE_NAME,
+ Attendees.ATTENDEE_EMAIL,
+ Attendees.ATTENDEE_RELATIONSHIP,
+ Attendees.ATTENDEE_TYPE,
+ Attendees.ATTENDEE_STATUS,
+ };
+ private static final int COLUMN_ATTENDEE_NAME = 0;
+ private static final int COLUMN_ATTENDEE_EMAIL = 1;
+ private static final int COLUMN_ATTENDEE_RELATIONSHIP = 2;
+ private static final int COLUMN_ATTENDEE_TYPE = 3;
+ private static final int COLUMN_ATTENDEE_STATUS = 4;
+ private static final String[] EXTENDED_PROJECTION = new String[] {
+ ExtendedProperties.NAME,
+ ExtendedProperties.VALUE,
+ };
+ private static final int COLUMN_NAME = 0;
+ private static final int COLUMN_VALUE = 1;
+
+ public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
+ super(cursor);
+ mResolver = resolver;
+ mProvider = null;
+ }
+
+ public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
+ super(cursor);
+ mResolver = null;
+ mProvider = provider;
+ }
+
+ public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
+ // we expect the cursor is already at the row we need to read from
+ final long eventId = cursor.getLong(cursor.getColumnIndexOrThrow(Events._ID));
+ ContentValues cv = new ContentValues();
+ cv.put(Events._ID, eventId);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HTML_URI);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TITLE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DESCRIPTION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_LOCATION);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, STATUS);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELF_ATTENDEE_STATUS);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, COMMENTS_URI);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTSTART);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DTEND);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DURATION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_TIMEZONE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBILITY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, TRANSPARENCY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ HAS_EXTENDED_PROPERTIES);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RRULE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RDATE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXRULE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXDATE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_EVENT);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
+ ORIGINAL_INSTANCE_TIME);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ORIGINAL_ALL_DAY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_DATE);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, HAS_ATTENDEE_DATA);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ GUESTS_CAN_INVITE_OTHERS);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_MODIFY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_SEE_GUESTS);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.URL);
+
+ Entity entity = new Entity(cv);
+ Cursor subCursor;
+ if (mResolver != null) {
+ subCursor = mResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+ "event_id=" + eventId, null, null);
+ } else {
+ subCursor = mProvider.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+ "event_id=" + eventId, null, null);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues reminderValues = new ContentValues();
+ reminderValues.put(Reminders.MINUTES, subCursor.getInt(COLUMN_MINUTES));
+ reminderValues.put(Reminders.METHOD, subCursor.getInt(COLUMN_METHOD));
+ entity.addSubValue(Reminders.CONTENT_URI, reminderValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ if (mResolver != null) {
+ subCursor = mResolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
+ "event_id=" + eventId, null /* selectionArgs */, null /* sortOrder */);
+ } else {
+ subCursor = mProvider.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
+ "event_id=" + eventId, null /* selectionArgs */, null /* sortOrder */);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues attendeeValues = new ContentValues();
+ attendeeValues.put(Attendees.ATTENDEE_NAME,
+ subCursor.getString(COLUMN_ATTENDEE_NAME));
+ attendeeValues.put(Attendees.ATTENDEE_EMAIL,
+ subCursor.getString(COLUMN_ATTENDEE_EMAIL));
+ attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
+ subCursor.getInt(COLUMN_ATTENDEE_RELATIONSHIP));
+ attendeeValues.put(Attendees.ATTENDEE_TYPE,
+ subCursor.getInt(COLUMN_ATTENDEE_TYPE));
+ attendeeValues.put(Attendees.ATTENDEE_STATUS,
+ subCursor.getInt(COLUMN_ATTENDEE_STATUS));
+ entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ if (mResolver != null) {
+ subCursor = mResolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
+ "event_id=" + eventId, null /* selectionArgs */, null /* sortOrder */);
+ } else {
+ subCursor = mProvider.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
+ "event_id=" + eventId, null /* selectionArgs */, null /* sortOrder */);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues extendedValues = new ContentValues();
+ extendedValues.put(ExtendedProperties.NAME, cursor.getString(COLUMN_NAME));
+ extendedValues.put(ExtendedProperties.VALUE,
+ cursor.getString(COLUMN_VALUE));
+ entity.addSubValue(ExtendedProperties.CONTENT_URI, extendedValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ cursor.moveToNext();
+ return entity;
+ }
+ }
}
/**
* Contains one entry per calendar event. Recurring events show up as a single entry.
*/
- public static final class Events implements BaseColumns, SyncConstValue,
- EventsColumns, CalendarsColumns {
+ public static final class Events implements BaseColumns, EventsColumns, CalendarsColumns {
private static final String[] FETCH_ENTRY_COLUMNS =
new String[] { Events._SYNC_ACCOUNT, Events._SYNC_ID };
@@ -532,8 +773,6 @@ public final class Calendar {
AttendeesColumns.ATTENDEE_TYPE,
AttendeesColumns.ATTENDEE_STATUS };
- private static CalendarClient sCalendarClient = null;
-
public static final Cursor query(ContentResolver cr, String[] projection) {
return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
}
@@ -585,7 +824,7 @@ public final class Calendar {
// where
String where = extractValue(event, "LOCATION");
- if (!StringUtils.isEmpty(where)) {
+ if (!TextUtils.isEmpty(where)) {
values.put(EVENT_LOCATION, where);
}
@@ -672,47 +911,6 @@ public final class Calendar {
}
/**
- * Returns a singleton instance of the CalendarClient used to fetch entries from the
- * calendar server.
- * @param cr The ContentResolver used to lookup the address of the calendar server in the
- * settings database.
- * @return The singleton instance of the CalendarClient used to fetch entries from the
- * calendar server.
- */
- private static synchronized CalendarClient getCalendarClient(ContentResolver cr) {
- if (sCalendarClient == null) {
- sCalendarClient = new CalendarClient(
- new AndroidGDataClient(cr),
- new XmlCalendarGDataParserFactory(new AndroidXmlParserFactory()));
- }
- return sCalendarClient;
- }
-
- /**
- * Extracts the attendees information out of event and adds it to a new ArrayList of columns
- * within the supplied ArrayList of rows. These rows are expected to be used within an
- * {@link ArrayListCursor}.
- */
- private static final void extractAttendeesIntoArrayList(EventEntry event,
- ArrayList<ArrayList> rows) {
- Log.d(TAG, "EVENT: " + event.toString());
- Vector<Who> attendees = (Vector<Who>) event.getAttendees();
-
- int numAttendees = attendees == null ? 0 : attendees.size();
-
- for (int i = 0; i < numAttendees; ++i) {
- Who attendee = attendees.elementAt(i);
- ArrayList row = new ArrayList();
- row.add(attendee.getValue());
- row.add(attendee.getEmail());
- row.add(attendee.getRelationship());
- row.add(attendee.getType());
- row.add(attendee.getStatus());
- rows.add(row);
- }
- }
-
- /**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
@@ -851,60 +1049,41 @@ public final class Calendar {
public static final String MAX_INSTANCE = "maxInstance";
/**
- * The minimum Julian day in the BusyBits table.
+ * The minimum Julian day in the EventDays table.
* <P>Type: INTEGER</P>
*/
- public static final String MIN_BUSYBITS = "minBusyBits";
+ public static final String MIN_EVENTDAYS = "minEventDays";
/**
- * The maximum Julian day in the BusyBits table.
+ * The maximum Julian day in the EventDays table.
* <P>Type: INTEGER</P>
*/
- public static final String MAX_BUSYBITS = "maxBusyBits";
+ public static final String MAX_EVENTDAYS = "maxEventDays";
}
-
+
public static final class CalendarMetaData implements CalendarMetaDataColumns {
}
-
- public interface BusyBitsColumns {
- /**
- * The Julian day number.
- * <P>Type: INTEGER (int)</P>
- */
- public static final String DAY = "day";
+ public interface EventDaysColumns {
/**
- * The 24 bits representing the 24 1-hour time slots in a day.
- * If an event in the Instances table overlaps part of a 1-hour
- * time slot then the corresponding bit is set. The first time slot
- * (12am to 1am) is bit 0. The last time slot (11pm to midnight)
- * is bit 23.
+ * The Julian starting day number.
* <P>Type: INTEGER (int)</P>
*/
- public static final String BUSYBITS = "busyBits";
+ public static final String STARTDAY = "startDay";
+ public static final String ENDDAY = "endDay";
- /**
- * The number of all-day events that occur on this day.
- * <P>Type: INTEGER (int)</P>
- */
- public static final String ALL_DAY_COUNT = "allDayCount";
}
-
- public static final class BusyBits implements BusyBitsColumns {
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/busybits/when");
- public static final String[] PROJECTION = { DAY, BUSYBITS, ALL_DAY_COUNT };
-
- // The number of minutes represented by one busy bit
- public static final int MINUTES_PER_BUSY_INTERVAL = 60;
-
- // The number of intervals in a day
- public static final int INTERVALS_PER_DAY = 24 * 60 / MINUTES_PER_BUSY_INTERVAL;
+ public static final class EventDays implements EventDaysColumns {
+ public static final Uri CONTENT_URI = Uri.parse("content://calendar/instances/groupbyday");
+
+ public static final String[] PROJECTION = { STARTDAY, ENDDAY };
+ public static final String SELECTION = "selected==1";
/**
- * Retrieves the busy bits for the Julian days starting at "startDay"
+ * Retrieves the days with events for the Julian days starting at "startDay"
* for "numDays".
- *
+ *
* @param cr the ContentResolver
* @param startDay the first Julian day in the range
* @param numDays the number of days to load (must be at least 1)
@@ -918,8 +1097,8 @@ public final class Calendar {
Uri.Builder builder = CONTENT_URI.buildUpon();
ContentUris.appendId(builder, startDay);
ContentUris.appendId(builder, endDay);
- return cr.query(builder.build(), PROJECTION, null /* selection */,
- null /* selection args */, DAY);
+ return cr.query(builder.build(), PROJECTION, SELECTION,
+ null /* selection args */, STARTDAY);
}
}
@@ -1025,23 +1204,25 @@ public final class Calendar {
/**
* The default sort order for this table
*/
- public static final String DEFAULT_SORT_ORDER = "alarmTime ASC,begin ASC,title ASC";
+ public static final String DEFAULT_SORT_ORDER = "begin ASC,title ASC";
}
public static final class CalendarAlerts implements BaseColumns,
CalendarAlertsColumns, EventsColumns, CalendarsColumns {
public static final String TABLE_NAME = "CalendarAlerts";
public static final Uri CONTENT_URI = Uri.parse("content://calendar/calendar_alerts");
-
+
/**
* This URI is for grouping the query results by event_id and begin
* time. This will return one result per instance of an event. So
* events with multiple alarms will appear just once, but multiple
* instances of a repeating event will show up multiple times.
*/
- public static final Uri CONTENT_URI_BY_INSTANCE =
+ public static final Uri CONTENT_URI_BY_INSTANCE =
Uri.parse("content://calendar/calendar_alerts/by_instance");
+ private static final boolean DEBUG = true;
+
public static final Uri insert(ContentResolver cr, long eventId,
long begin, long end, long alarmTime, int minutes) {
ContentValues values = new ContentValues();
@@ -1059,15 +1240,15 @@ public final class Calendar {
}
public static final Cursor query(ContentResolver cr, String[] projection,
- String selection, String[] selectionArgs) {
+ String selection, String[] selectionArgs, String sortOrder) {
return cr.query(CONTENT_URI, projection, selection, selectionArgs,
- DEFAULT_SORT_ORDER);
+ sortOrder);
}
-
+
/**
* Finds the next alarm after (or equal to) the given time and returns
* the time of that alarm or -1 if no such alarm exists.
- *
+ *
* @param cr the ContentResolver
* @param millis the time in UTC milliseconds
* @return the next alarm time greater than or equal to "millis", or -1
@@ -1078,7 +1259,7 @@ public final class Calendar {
// TODO: construct an explicit SQL query so that we can add
// "LIMIT 1" to the end and get just one result.
String[] projection = new String[] { ALARM_TIME };
- Cursor cursor = query(cr, projection, selection, null);
+ Cursor cursor = query(cr, projection, selection, null, ALARM_TIME + " ASC");
long alarmTime = -1;
try {
if (cursor != null && cursor.moveToFirst()) {
@@ -1091,13 +1272,13 @@ public final class Calendar {
}
return alarmTime;
}
-
+
/**
* Searches the CalendarAlerts table for alarms that should have fired
* but have not and then reschedules them. This method can be called
* at boot time to restore alarms that may have been lost due to a
* phone reboot.
- *
+ *
* @param cr the ContentResolver
* @param context the Context
* @param manager the AlarmManager
@@ -1107,53 +1288,68 @@ public final class Calendar {
// Get all the alerts that have been scheduled but have not fired
// and should have fired by now and are not too old.
long now = System.currentTimeMillis();
- long ancient = now - 24 * DateUtils.HOUR_IN_MILLIS;
+ long ancient = now - DateUtils.DAY_IN_MILLIS;
String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED
+ " AND " + CalendarAlerts.ALARM_TIME + "<" + now
+ " AND " + CalendarAlerts.ALARM_TIME + ">" + ancient
+ " AND " + CalendarAlerts.END + ">=" + now;
String[] projection = new String[] {
- _ID,
- BEGIN,
- END,
ALARM_TIME,
};
- Cursor cursor = CalendarAlerts.query(cr, projection, selection, null);
+
+ // TODO: construct an explicit SQL query so that we can add
+ // "GROUPBY" instead of doing a sort and de-dup
+ Cursor cursor = CalendarAlerts.query(cr, projection, selection, null, "alarmTime ASC");
if (cursor == null) {
return;
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+
+ if (DEBUG) {
Log.d(TAG, "missed alarms found: " + cursor.getCount());
}
-
+
try {
+ long alarmTime = -1;
+
while (cursor.moveToNext()) {
- long id = cursor.getLong(0);
- long begin = cursor.getLong(1);
- long end = cursor.getLong(2);
- long alarmTime = cursor.getLong(3);
- Uri uri = ContentUris.withAppendedId(CONTENT_URI, id);
- Intent intent = new Intent(android.provider.Calendar.EVENT_REMINDER_ACTION);
- intent.setData(uri);
- intent.putExtra(android.provider.Calendar.EVENT_BEGIN_TIME, begin);
- intent.putExtra(android.provider.Calendar.EVENT_END_TIME, end);
- PendingIntent sender = PendingIntent.getBroadcast(context,
- 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- Log.w(TAG, "rescheduling missed alarm, id: " + id + " begin: " + begin
- + " end: " + end + " alarmTime: " + alarmTime);
- manager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);
+ long newAlarmTime = cursor.getLong(0);
+ if (alarmTime != newAlarmTime) {
+ if (DEBUG) {
+ Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
+ }
+ scheduleAlarm(context, manager, newAlarmTime);
+ alarmTime = newAlarmTime;
+ }
}
} finally {
cursor.close();
}
-
}
-
+
+ public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
+ if (DEBUG) {
+ Time time = new Time();
+ time.set(alarmTime);
+ String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
+ Log.d(TAG, "Schedule alarm at " + alarmTime + " " + schedTime);
+ }
+
+ if (manager == null) {
+ manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ }
+
+ Intent intent = new Intent(android.provider.Calendar.EVENT_REMINDER_ACTION);
+ intent.putExtra(android.provider.Calendar.CalendarAlerts.ALARM_TIME, alarmTime);
+ PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ manager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
+ }
+
/**
* Searches for an entry in the CalendarAlerts table that matches
* the given event id, begin time and alarm time. If one is found
* then this alarm already exists and this method returns true.
- *
+ *
* @param cr the ContentResolver
* @param eventId the event id to match
* @param begin the start time of the event in UTC millis
@@ -1169,7 +1365,7 @@ public final class Calendar {
// TODO: construct an explicit SQL query so that we can add
// "LIMIT 1" to the end and get just one result.
String[] projection = new String[] { CalendarAlerts.ALARM_TIME };
- Cursor cursor = query(cr, projection, selection, null);
+ Cursor cursor = query(cr, projection, selection, null, null);
boolean found = false;
try {
if (cursor != null && cursor.getCount() > 0) {
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
index 4134dc2..75936a1 100644
--- a/core/java/android/provider/Checkin.java
+++ b/core/java/android/provider/Checkin.java
@@ -23,7 +23,6 @@ import android.content.ContentValues;
import android.database.SQLException;
import android.net.Uri;
import android.os.SystemClock;
-import android.server.data.CrashData;
import android.util.Log;
import java.io.ByteArrayOutputStream;
@@ -74,7 +73,6 @@ public final class Checkin {
CARRIER_BUG_REPORT,
CHECKIN_FAILURE,
CHECKIN_SUCCESS,
- CPUFREQ_STATS,
FOTA_BEGIN,
FOTA_FAILURE,
FOTA_INSTALL,
@@ -268,59 +266,4 @@ public final class Checkin {
/** {@link SystemClock#elapsedRealtime} of the last time a crash report failed. */
static private volatile long sLastCrashFailureRealtime = -MIN_CRASH_FAILURE_RETRY;
-
- /**
- * Helper function to report a crash.
- *
- * @param resolver from {@link android.content.Context#getContentResolver}
- * @param crash data from {@link android.server.data.CrashData}
- * @return URI of the crash report that was added
- */
- static public Uri reportCrash(ContentResolver resolver, byte[] crash) {
- try {
- // If we are in a situation where crash reports fail (such as a full disk),
- // it's important that we don't get into a loop trying to report failures.
- // So discard all crash reports for a few seconds after reporting fails.
- long realtime = SystemClock.elapsedRealtime();
- if (realtime - sLastCrashFailureRealtime < MIN_CRASH_FAILURE_RETRY) {
- Log.e(TAG, "Crash logging skipped, too soon after logging failure");
- return null;
- }
-
- // HACK: we don't support BLOB values, so base64 encode it.
- byte[] encoded = Base64.encodeBase64(crash);
- ContentValues values = new ContentValues();
- values.put(Crashes.DATA, new String(encoded));
- Uri uri = resolver.insert(Crashes.CONTENT_URI, values);
- if (uri == null) {
- Log.e(TAG, "Error reporting crash");
- sLastCrashFailureRealtime = SystemClock.elapsedRealtime();
- }
- return uri;
- } catch (Throwable t) {
- // To avoid an infinite crash-reporting loop, swallow all errors and exceptions.
- Log.e(TAG, "Error reporting crash: " + t);
- sLastCrashFailureRealtime = SystemClock.elapsedRealtime();
- return null;
- }
- }
-
- /**
- * Report a crash in CrashData format.
- *
- * @param resolver from {@link android.content.Context#getContentResolver}
- * @param crash data to report
- * @return URI of the crash report that was added
- */
- static public Uri reportCrash(ContentResolver resolver, CrashData crash) {
- try {
- ByteArrayOutputStream data = new ByteArrayOutputStream();
- crash.write(new DataOutputStream(data));
- return reportCrash(resolver, data.toByteArray());
- } catch (Throwable t) {
- // Swallow all errors and exceptions when writing crash report
- Log.e(TAG, "Error writing crash: " + t);
- return null;
- }
- }
}
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index 1a38166..a29ecb5 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -1334,8 +1334,26 @@ public class Contacts {
}
/**
+ * TODO find a place to put the canonical version of these.
+ */
+ interface ProviderNames {
+ //
+ //NOTE: update Contacts.java with new providers when they're added.
+ //
+ String YAHOO = "Yahoo";
+ String GTALK = "GTalk";
+ String MSN = "MSN";
+ String ICQ = "ICQ";
+ String AIM = "AIM";
+ String XMPP = "XMPP";
+ String JABBER = "JABBER";
+ String SKYPE = "SKYPE";
+ String QQ = "QQ";
+ }
+
+ /**
* This looks up the provider name defined in
- * {@link android.provider.Im.ProviderNames} from the predefined IM protocol id.
+ * from the predefined IM protocol id.
* This is used for interacting with the IM application.
*
* @param protocol the protocol ID
@@ -1348,21 +1366,21 @@ public class Contacts {
public static String lookupProviderNameFromId(int protocol) {
switch (protocol) {
case PROTOCOL_GOOGLE_TALK:
- return Im.ProviderNames.GTALK;
+ return ProviderNames.GTALK;
case PROTOCOL_AIM:
- return Im.ProviderNames.AIM;
+ return ProviderNames.AIM;
case PROTOCOL_MSN:
- return Im.ProviderNames.MSN;
+ return ProviderNames.MSN;
case PROTOCOL_YAHOO:
- return Im.ProviderNames.YAHOO;
+ return ProviderNames.YAHOO;
case PROTOCOL_ICQ:
- return Im.ProviderNames.ICQ;
+ return ProviderNames.ICQ;
case PROTOCOL_JABBER:
- return Im.ProviderNames.JABBER;
+ return ProviderNames.JABBER;
case PROTOCOL_SKYPE:
- return Im.ProviderNames.SKYPE;
+ return ProviderNames.SKYPE;
case PROTOCOL_QQ:
- return Im.ProviderNames.QQ;
+ return ProviderNames.QQ;
}
return null;
}
@@ -1532,7 +1550,35 @@ public class Contacts {
* @deprecated see {@link android.provider.ContactsContract}
*/
@Deprecated
- public interface PresenceColumns extends Im.CommonPresenceColumns {
+ public interface PresenceColumns {
+ /**
+ * The priority, an integer, used by XMPP presence
+ * <P>Type: INTEGER</P>
+ */
+ String PRIORITY = "priority";
+
+ /**
+ * The server defined status.
+ * <P>Type: INTEGER (one of the values below)</P>
+ */
+ String PRESENCE_STATUS = ContactsContract.StatusUpdates.PRESENCE;
+
+ /**
+ * Presence Status definition
+ */
+ int OFFLINE = ContactsContract.StatusUpdates.OFFLINE;
+ int INVISIBLE = ContactsContract.StatusUpdates.INVISIBLE;
+ int AWAY = ContactsContract.StatusUpdates.AWAY;
+ int IDLE = ContactsContract.StatusUpdates.IDLE;
+ int DO_NOT_DISTURB = ContactsContract.StatusUpdates.DO_NOT_DISTURB;
+ int AVAILABLE = ContactsContract.StatusUpdates.AVAILABLE;
+
+ /**
+ * The user defined status line.
+ * <P>Type: TEXT</P>
+ */
+ String PRESENCE_CUSTOM_STATUS = ContactsContract.StatusUpdates.STATUS;
+
/**
* The IM service the presence is coming from. Formatted using either
* {@link Contacts.ContactMethods#encodePredefinedImProtocol} or
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a56bb45..7fb9daf 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -23,14 +23,19 @@ import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.CursorEntityIterator;
+import android.content.Entity;
+import android.content.EntityIterator;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteException;
import android.graphics.Rect;
import android.net.Uri;
import android.os.RemoteException;
-import android.provider.ContactsContract.CommonDataKinds.Email;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.View;
@@ -57,7 +62,8 @@ import java.io.InputStream;
* </li>
* <li>
* A row in the {@link RawContacts} table represents a set of Data describing a
- * person and associated with a single account.
+ * person and associated with a single account (for example, a single Gmail
+ * account).
* </li>
* <li>
* A row in the {@link Contacts} table represents an aggregate of one or more
@@ -240,6 +246,9 @@ public final class ContactsContract {
}
/**
+ * Columns of {@link ContactsContract.Contacts} that track the user's
+ * preferences for, or interactions with, the contact.
+ *
* @see Contacts
* @see RawContacts
* @see ContactsContract.Data
@@ -266,20 +275,25 @@ public final class ContactsContract {
public static final String STARRED = "starred";
/**
- * A custom ringtone associated with a contact. Not always present.
+ * URI for a custom ringtone associated with the contact. If null or missing,
+ * the default ringtone is used.
* <P>Type: TEXT (URI to the ringtone)</P>
*/
public static final String CUSTOM_RINGTONE = "custom_ringtone";
/**
- * Whether the contact should always be sent to voicemail. Not always
- * present.
+ * Whether the contact should always be sent to voicemail. If missing,
+ * defaults to false.
* <P>Type: INTEGER (0 for false, 1 for true)</P>
*/
public static final String SEND_TO_VOICEMAIL = "send_to_voicemail";
}
/**
+ * Columns of {@link ContactsContract.Contacts} that refer to intrinsic
+ * properties of the contact, as opposed to the user-specified options
+ * found in {@link ContactOptionsColumns}.
+ *
* @see Contacts
* @see ContactsContract.Data
* @see PhoneLookup
@@ -290,7 +304,14 @@ public final class ContactsContract {
* The display name for the contact.
* <P>Type: TEXT</P>
*/
- public static final String DISPLAY_NAME = "display_name";
+ public static final String DISPLAY_NAME = ContactNameColumns.DISPLAY_NAME_PRIMARY;
+
+ /**
+ * Reference to the row in the RawContacts table holding the contact name.
+ * <P>Type: INTEGER REFERENCES raw_contacts(_id)</P>
+ * @hide
+ */
+ public static final String NAME_RAW_CONTACT_ID = "name_raw_contact_id";
/**
* Reference to the row in the data table holding the photo.
@@ -365,6 +386,126 @@ public final class ContactsContract {
}
/**
+ * Constants for various styles of combining given name, family name etc into
+ * a full name. For example, the western tradition follows the pattern
+ * 'given name' 'middle name' 'family name' with the alternative pattern being
+ * 'family name', 'given name' 'middle name'. The CJK tradition is
+ * 'family name' 'middle name' 'given name', with Japanese favoring a space between
+ * the names and Chinese omitting the space.
+ * @hide
+ */
+ public interface FullNameStyle {
+ public static final int UNDEFINED = 0;
+ public static final int WESTERN = 1;
+
+ /**
+ * Used if the name is written in Hanzi/Kanji/Hanja and we could not determine
+ * which specific language it belongs to: Chinese, Japanese or Korean.
+ */
+ public static final int CJK = 2;
+
+ public static final int CHINESE = 3;
+ public static final int JAPANESE = 4;
+ public static final int KOREAN = 5;
+ }
+
+ /**
+ * Constants for various styles of capturing the pronunciation of a person's name.
+ * @hide
+ */
+ public interface PhoneticNameStyle {
+ public static final int UNDEFINED = 0;
+
+ /**
+ * Pinyin is a phonetic method of entering Chinese characters. Typically not explicitly
+ * shown in UIs, but used for searches and sorting.
+ */
+ public static final int PINYIN = 3;
+
+ /**
+ * Hiragana and Katakana are two common styles of writing out the pronunciation
+ * of a Japanese names.
+ */
+ public static final int JAPANESE = 4;
+
+ /**
+ * Hangul is the Korean phonetic alphabet.
+ */
+ public static final int KOREAN = 5;
+ }
+
+ /**
+ * Types of data used to produce the display name for a contact. Listed in the order
+ * of increasing priority.
+ *
+ * @hide
+ */
+ public interface DisplayNameSources {
+ public static final int UNDEFINED = 0;
+ public static final int EMAIL = 10;
+ public static final int PHONE = 20;
+ public static final int ORGANIZATION = 30;
+ public static final int NICKNAME = 35;
+ public static final int STRUCTURED_NAME = 40;
+ }
+
+ /**
+ * Contact name and contact name metadata columns in the RawContacts table.
+ *
+ * @see Contacts
+ * @see RawContacts
+ * @hide
+ */
+ protected interface ContactNameColumns {
+
+ /**
+ * The kind of data that is used as the display name for the contact, see
+ * DisplayNameSources.
+ */
+ public static final String DISPLAY_NAME_SOURCE = "display_name_source";
+
+ /**
+ * The default text shown as the contact's display name. It is based on
+ * available data, see {@link #DISPLAY_NAME_SOURCE}.
+ */
+ public static final String DISPLAY_NAME_PRIMARY = "display_name";
+
+ /**
+ * Alternative representation of the display name. If display name is
+ * based on the structured name and the structured name follows
+ * the Western full name style, then this field contains the "family name first"
+ * version of the full name. Otherwise, it is the same as DISPLAY_NAME_PRIMARY.
+ */
+ public static final String DISPLAY_NAME_ALTERNATIVE = "display_name_alt";
+
+ /**
+ * The type of alphabet used to capture the phonetic name. See
+ * PhoneticNameStyle.
+ */
+ public static final String PHONETIC_NAME_STYLE = "phonetic_name_style";
+
+ /**
+ * Pronunciation of the full name. See PhoneticNameStyle.
+ */
+ public static final String PHONETIC_NAME = "phonetic_name";
+
+ /**
+ * Sort key that takes into account locale-based traditions for sorting
+ * names in address books. More specifically, for Chinese names
+ * the sort key is the name's Pinyin spelling; for Japanese names
+ * it is the Hiragana version of the phonetic name.
+ */
+ public static final String SORT_KEY_PRIMARY = "sort_key";
+
+ /**
+ * Sort key based on the alternative representation of the full name,
+ * specifically the one using the 'family name first' format for
+ * Western names.
+ */
+ public static final String SORT_KEY_ALTERNATIVE = "sort_key_alt";
+ }
+
+ /**
* Constants for the contacts table, which contains a record per aggregate
* of raw contacts representing the same person.
* <h3>Operations</h3>
@@ -423,13 +564,21 @@ public final class ContactsContract {
* row id changed as a result of a sync or aggregation.</td>
* </tr>
* <tr>
+ * <td>long</td>
+ * <td>NAME_RAW_CONTACT_ID</td>
+ * <td>read-only</td>
+ * <td>The ID of the raw contact that contributes the display name
+ * to the aggregate contact. During aggregation one of the constituent
+ * raw contacts is chosen using a heuristic: a longer name or a name
+ * with more diacritic marks or more upper case characters is chosen.</td>
+ * </tr>
+ * <tr>
* <td>String</td>
- * <td>{@link #DISPLAY_NAME}</td>
+ * <td>DISPLAY_NAME_PRIMARY</td>
* <td>read-only</td>
- * <td>The display name for the contact. During aggregation display name is
- * computed from display names of constituent raw contacts using a
- * heuristic: a longer name or a name with more diacritic marks or more
- * upper case characters is chosen.</td>
+ * <td>The display name for the contact. It is the display name
+ * contributed by the raw contact referred to by the NAME_RAW_CONTACT_ID
+ * column.</td>
* </tr>
* <tr>
* <td>long</td>
@@ -555,7 +704,7 @@ public final class ContactsContract {
* </table>
*/
public static class Contacts implements BaseColumns, ContactsColumns,
- ContactOptionsColumns, ContactStatusColumns {
+ ContactOptionsColumns, ContactNameColumns, ContactStatusColumns {
/**
* This utility class cannot be instantiated
*/
@@ -969,7 +1118,7 @@ public final class ContactsContract {
* removes the raw contact from its aggregate contact.
* The sync adapter then deletes the raw contact from the server and
* finalizes phone-side deletion by calling {@code resolver.delete(...)}
- * again and passing the {@link #CALLER_IS_SYNCADAPTER} query parameter.<p>
+ * again and passing the {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter.<p>
* <p>Some sync adapters are read-only, meaning that they only sync server-side
* changes to the phone, but not the reverse. If one of those raw contacts
* is marked for deletion, it will remain on the phone. However it will be
@@ -1215,7 +1364,7 @@ public final class ContactsContract {
* </table>
*/
public static final class RawContacts implements BaseColumns, RawContactsColumns,
- ContactOptionsColumns, SyncColumns {
+ ContactOptionsColumns, ContactNameColumns, SyncColumns {
/**
* This utility class cannot be instantiated
*/
@@ -1313,7 +1462,7 @@ public final class ContactsContract {
* <p>
* A sub-directory of a single raw contact that contains all of their
* {@link ContactsContract.Data} rows. To access this directory append
- * {@link Entity#CONTENT_DIRECTORY} to the contact URI. See
+ * {@link #CONTENT_DIRECTORY} to the contact URI. See
* {@link RawContactsEntity} for a stand-alone table containing the same
* data.
* </p>
@@ -1351,6 +1500,112 @@ public final class ContactsContract {
*/
public static final String DATA_ID = "data_id";
}
+
+ public static EntityIterator newEntityIterator(Cursor cursor) {
+ return new EntityIteratorImpl(cursor);
+ }
+
+ private static class EntityIteratorImpl extends CursorEntityIterator {
+ private static final String[] DATA_KEYS = new String[]{
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15,
+ Data.SYNC1,
+ Data.SYNC2,
+ Data.SYNC3,
+ Data.SYNC4};
+
+ public EntityIteratorImpl(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public android.content.Entity getEntityAndIncrementCursor(Cursor cursor)
+ throws RemoteException {
+ final int columnRawContactId = cursor.getColumnIndexOrThrow(RawContacts._ID);
+ final long rawContactId = cursor.getLong(columnRawContactId);
+
+ // we expect the cursor is already at the row we need to read from
+ ContentValues cv = new ContentValues();
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_NAME);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_TYPE);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _ID);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, VERSION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SOURCE_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC1);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC2);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC3);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC4);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DELETED);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, CONTACT_ID);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, STARRED);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, IS_RESTRICTED);
+ android.content.Entity contact = new android.content.Entity(cv);
+
+ // read data rows until the contact id changes
+ do {
+ if (rawContactId != cursor.getLong(columnRawContactId)) {
+ break;
+ }
+ // add the data to to the contact
+ cv = new ContentValues();
+ cv.put(Data._ID, cursor.getLong(cursor.getColumnIndexOrThrow(Entity.DATA_ID)));
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ Data.RES_PACKAGE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Data.MIMETYPE);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, Data.IS_PRIMARY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
+ Data.IS_SUPER_PRIMARY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, Data.DATA_VERSION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ CommonDataKinds.GroupMembership.GROUP_SOURCE_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ Data.DATA_VERSION);
+ for (String key : DATA_KEYS) {
+ final int columnIndex = cursor.getColumnIndexOrThrow(key);
+ if (cursor.isNull(columnIndex)) {
+ // don't put anything
+ } else {
+ try {
+ cv.put(key, cursor.getString(columnIndex));
+ } catch (SQLiteException e) {
+ cv.put(key, cursor.getBlob(columnIndex));
+ }
+ }
+ // TODO: go back to this version of the code when bug
+ // http://b/issue?id=2306370 is fixed.
+// if (cursor.isNull(columnIndex)) {
+// // don't put anything
+// } else if (cursor.isLong(columnIndex)) {
+// values.put(key, cursor.getLong(columnIndex));
+// } else if (cursor.isFloat(columnIndex)) {
+// values.put(key, cursor.getFloat(columnIndex));
+// } else if (cursor.isString(columnIndex)) {
+// values.put(key, cursor.getString(columnIndex));
+// } else if (cursor.isBlob(columnIndex)) {
+// values.put(key, cursor.getBlob(columnIndex));
+// }
+ }
+ contact.addSubValue(ContactsContract.Data.CONTENT_URI, cv);
+ } while (cursor.moveToNext());
+
+ return contact;
+ }
+
+ }
}
/**
@@ -1359,18 +1614,60 @@ public final class ContactsContract {
* @see StatusUpdates
* @see ContactsContract.Data
*/
- protected interface StatusColumns extends Im.CommonPresenceColumns {
+ protected interface StatusColumns {
/**
* Contact's latest presence level.
* <P>Type: INTEGER (one of the values below)</P>
*/
- public static final String PRESENCE = PRESENCE_STATUS;
+ public static final String PRESENCE = "mode";
+
+ /**
+ * @deprecated use {@link #PRESENCE}
+ */
+ @Deprecated
+ public static final String PRESENCE_STATUS = PRESENCE;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int OFFLINE = 0;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int INVISIBLE = 1;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int AWAY = 2;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int IDLE = 3;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int DO_NOT_DISTURB = 4;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int AVAILABLE = 5;
/**
* Contact latest status update.
* <p>Type: TEXT</p>
*/
- public static final String STATUS = PRESENCE_CUSTOM_STATUS;
+ public static final String STATUS = "status";
+
+ /**
+ * @deprecated use {@link #STATUS}
+ */
+ @Deprecated
+ public static final String PRESENCE_CUSTOM_STATUS = STATUS;
/**
* The absolute time in milliseconds when the latest status was inserted/updated.
@@ -1426,7 +1723,7 @@ public final class ContactsContract {
public static final String RAW_CONTACT_ID = "raw_contact_id";
/**
- * Whether this is the primary entry of its kind for the raw contact it belongs to
+ * Whether this is the primary entry of its kind for the raw contact it belongs to.
* <P>Type: INTEGER (if set, non-0 means true)</P>
*/
public static final String IS_PRIMARY = "is_primary";
@@ -1475,7 +1772,10 @@ public final class ContactsContract {
public static final String DATA13 = "data13";
/** Generic data column, the meaning is {@link #MIMETYPE} specific */
public static final String DATA14 = "data14";
- /** Generic data column, the meaning is {@link #MIMETYPE} specific */
+ /**
+ * Generic data column, the meaning is {@link #MIMETYPE} specific. By convention,
+ * this field is used to store BLOBs (binary data).
+ */
public static final String DATA15 = "data15";
/** Generic column for use by sync adapters. */
@@ -1494,30 +1794,35 @@ public final class ContactsContract {
* @see ContactsContract.Data
*/
protected interface DataColumnsWithJoins extends BaseColumns, DataColumns, StatusColumns,
- RawContactsColumns, ContactsColumns, ContactOptionsColumns, ContactStatusColumns {
-
+ RawContactsColumns, ContactsColumns, ContactNameColumns, ContactOptionsColumns,
+ ContactStatusColumns {
}
/**
* <p>
* Constants for the data table, which contains data points tied to a raw
- * contact. For example, a phone number or email address.
+ * contact. Each row of the data table is typically used to store a single
+ * piece of contact
+ * information (such as a phone number) and its
+ * associated metadata (such as whether it is a work or home number).
* </p>
* <h3>Data kinds</h3>
* <p>
- * Data is a generic table that can hold all kinds of data. Sync adapters
- * and applications can introduce their own data kinds. The kind of data
- * stored in a particular row is determined by the mime type in the row.
- * Fields from {@link #DATA1} through {@link #DATA15} are generic columns
- * whose specific use is determined by the kind of data stored in the row.
+ * Data is a generic table that can hold any kind of contact data.
+ * The kind of data stored in a given row is specified by the row's
+ * {@link #MIMETYPE} value, which determines the meaning of the
+ * generic columns {@link #DATA1} through
+ * {@link #DATA15}.
* For example, if the data kind is
- * {@link CommonDataKinds.Phone Phone.CONTENT_ITEM_TYPE}, then DATA1 stores the
+ * {@link CommonDataKinds.Phone Phone.CONTENT_ITEM_TYPE}, then the column
+ * {@link #DATA1} stores the
* phone number, but if the data kind is
- * {@link CommonDataKinds.Email Email.CONTENT_ITEM_TYPE}, then DATA1 stores the
- * email address.
+ * {@link CommonDataKinds.Email Email.CONTENT_ITEM_TYPE}, then {@link #DATA1}
+ * stores the email address.
+ * Sync adapters and applications can introduce their own data kinds.
* </p>
* <p>
- * ContactsContract defines a small number of common data kinds, e.g.
+ * ContactsContract defines a small number of pre-defined data kinds, e.g.
* {@link CommonDataKinds.Phone}, {@link CommonDataKinds.Email} etc. As a
* convenience, these classes define data kind specific aliases for DATA1 etc.
* For example, {@link CommonDataKinds.Phone Phone.NUMBER} is the same as
@@ -1534,8 +1839,8 @@ public final class ContactsContract {
* By convention, {@link #DATA15} is used for storing BLOBs (binary data).
* </p>
* <p>
- * Typically you should refrain from introducing new kinds of data for 3rd
- * party account types. For example, if you add a data row for
+ * Typically you should refrain from introducing new kinds of data for an other
+ * party's account types. For example, if you add a data row for
* "favorite song" to a raw contact owned by a Google account, it will not
* get synced to the server, because the Google sync adapter does not know
* how to handle this data kind. Thus new data kinds are typically
@@ -1672,6 +1977,10 @@ public final class ContactsContract {
* </dd>
* </dl>
* <h2>Columns</h2>
+ * <p>
+ * Many columns are available via a {@link Data#CONTENT_URI} query. For best performance you
+ * should explicitly specify a projection to only those columns that you need.
+ * </p>
* <table class="jd-sumtable">
* <tr>
* <th colspan='4'>Data</th>
@@ -1681,7 +1990,7 @@ public final class ContactsContract {
* <td style="width: 20em;">{@link #_ID}</td>
* <td style="width: 5em;">read-only</td>
* <td>Row ID. Sync adapter should try to preserve row IDs during updates. In other words,
- * it would be a bad idea to delete and reinsert a data rows. A sync adapter should
+ * it would be a bad idea to delete and reinsert a data row. A sync adapter should
* always do an update instead.</td>
* </tr>
* <tr>
@@ -1713,21 +2022,15 @@ public final class ContactsContract {
* <td>long</td>
* <td>{@link #RAW_CONTACT_ID}</td>
* <td>read/write-once</td>
- * <td>A reference to the {@link RawContacts#_ID} that this data belongs to.</td>
- * </tr>
- * <tr>
- * <td>long</td>
- * <td>{@link #CONTACT_ID}</td>
- * <td>read-only</td>
- * <td>A reference to the {@link ContactsContract.Contacts#_ID} that this data row belongs
- * to. It is obtained through a join with RawContacts.</td>
+ * <td>The id of the row in the {@link RawContacts} table that this data belongs to.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #IS_PRIMARY}</td>
* <td>read/write</td>
* <td>Whether this is the primary entry of its kind for the raw contact it belongs to.
- * "1" if true, "0" if false.</td>
+ * "1" if true, "0" if false.
+ * </td>
* </tr>
* <tr>
* <td>int</td>
@@ -1735,7 +2038,9 @@ public final class ContactsContract {
* <td>read/write</td>
* <td>Whether this is the primary entry of its kind for the aggregate
* contact it belongs to. Any data record that is "super primary" must
- * also be "primary".</td>
+ * also be "primary". For example, the super-primary entry may be
+ * interpreted as the default contact value of its kind (for example,
+ * the default phone number to use for the contact).</td>
* </tr>
* <tr>
* <td>int</td>
@@ -1764,7 +2069,19 @@ public final class ContactsContract {
* {@link #DATA15}
* </td>
* <td>read/write</td>
- * <td>Generic data columns, the meaning is {@link #MIMETYPE} specific.</td>
+ * <td>
+ * <p>
+ * Generic data columns. The meaning of each column is determined by the
+ * {@link #MIMETYPE}. By convention, {@link #DATA15} is used for storing
+ * BLOBs (binary data).
+ * </p>
+ * <p>
+ * Data columns whose meaning is not explicitly defined for a given MIMETYPE
+ * should not be used. There is no guarantee that any sync adapter will
+ * preserve them. Sync adapters themselves should not use such columns either,
+ * but should instead use {@link #SYNC1}-{@link #SYNC4}.
+ * </p>
+ * </td>
* </tr>
* <tr>
* <td>Any type</td>
@@ -1781,6 +2098,10 @@ public final class ContactsContract {
* </tr>
* </table>
*
+ * <p>
+ * Some columns from the most recent associated status update are also available
+ * through an implicit join.
+ * </p>
* <table class="jd-sumtable">
* <tr>
* <th colspan='4'>Join with {@link StatusUpdates}</th>
@@ -1833,18 +2154,26 @@ public final class ContactsContract {
* </table>
*
* <p>
- * Columns from the associated raw contact are also available through an
- * implicit join.
+ * Some columns from the associated raw contact are also available through an
+ * implicit join. The other columns are excluded as uninteresting in this
+ * context.
* </p>
*
* <table class="jd-sumtable">
* <tr>
- * <th colspan='4'>Join with {@link RawContacts}</th>
+ * <th colspan='4'>Join with {@link ContactsContract.RawContacts}</th>
* </tr>
* <tr>
- * <td style="width: 7em;">int</td>
- * <td style="width: 20em;">{@link #AGGREGATION_MODE}</td>
+ * <td style="width: 7em;">long</td>
+ * <td style="width: 20em;">{@link #CONTACT_ID}</td>
* <td style="width: 5em;">read-only</td>
+ * <td>The id of the row in the {@link Contacts} table that this data belongs
+ * to.</td>
+ * </tr>
+ * <tr>
+ * <td>int</td>
+ * <td>{@link #AGGREGATION_MODE}</td>
+ * <td>read-only</td>
* <td>See {@link RawContacts}.</td>
* </tr>
* <tr>
@@ -1856,13 +2185,18 @@ public final class ContactsContract {
* </table>
*
* <p>
- * Columns from the associated aggregated contact are also available through an
- * implicit join.
+ * The ID column for the associated aggregated contact table
+ * {@link ContactsContract.Contacts} is available
+ * via the implicit join to the {@link RawContacts} table, see above.
+ * The remaining columns from this table are also
+ * available, through an implicit join. This
+ * facilitates lookup by
+ * the value of a single data element, such as the email address.
* </p>
*
* <table class="jd-sumtable">
* <tr>
- * <th colspan='4'>Join with {@link Contacts}</th>
+ * <th colspan='4'>Join with {@link ContactsContract.Contacts}</th>
* </tr>
* <tr>
* <td style="width: 7em;">String</td>
@@ -1969,24 +2303,30 @@ public final class ContactsContract {
private Data() {}
/**
- * The content:// style URI for this table
+ * The content:// style URI for this table, which requests a directory
+ * of data rows matching the selection criteria.
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
/**
- * The MIME type of {@link #CONTENT_URI} providing a directory of data.
+ * The MIME type of the results from {@link #CONTENT_URI}.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/data";
/**
+ * <p>
* If {@link #FOR_EXPORT_ONLY} is explicitly set to "1", returned Cursor toward
* Data.CONTENT_URI contains only exportable data.
- *
+ * </p>
+ * <p>
* This flag is useful (currently) only for vCard exporter in Contacts app, which
* needs to exclude "un-exportable" data from available data to export, while
* Contacts app itself has priviledge to access all data including "un-exportable"
* ones and providers return all of them regardless of the callers' intention.
- * <P>Type: INTEGER</p>
+ * </p>
+ * <p>
+ * Type: INTEGER
+ * </p>
*
* @hide Maybe available only in Eclair and not really ready for public use.
* TODO: remove, or implement this feature completely. As of now (Eclair),
@@ -1995,9 +2335,17 @@ public final class ContactsContract {
public static final String FOR_EXPORT_ONLY = "for_export_only";
/**
+ * <p>
* Build a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}
* style {@link Uri} for the parent {@link android.provider.ContactsContract.Contacts}
* entry of the given {@link ContactsContract.Data} entry.
+ * </p>
+ * <p>
+ * Returns the Uri for the contact in the first entry returned by
+ * {@link ContentResolver#query(Uri, String[], String, String[], String)}
+ * for the provided {@code dataUri}. If the query returns null or empty
+ * results, silently returns null.
+ * </p>
*/
public static Uri getContactLookupUri(ContentResolver resolver, Uri dataUri) {
final Cursor cursor = resolver.query(dataUri, new String[] {
@@ -2020,7 +2368,7 @@ public final class ContactsContract {
/**
* <p>
- * Constants for the raw contacts entities table, which can be though of as
+ * Constants for the raw contacts entities table, which can be thought of as
* an outer join of the raw_contacts table with the data table. It is a strictly
* read-only table.
* </p>
@@ -2765,6 +3113,21 @@ public final class ContactsContract {
* <P>Type: TEXT</P>
*/
public static final String PHONETIC_FAMILY_NAME = DATA9;
+
+ /**
+ * The style used for combining given/middle/family name into a full name.
+ * See {@link ContactsContract.FullNameStyle}.
+ *
+ * @hide
+ */
+ public static final String FULL_NAME_STYLE = DATA10;
+
+ /**
+ * The alphabet used for capturing the phonetic name.
+ * See ContactsContract.PhoneticNameStyle.
+ * @hide
+ */
+ public static final String PHONETIC_NAME_STYLE = DATA11;
}
/**
@@ -3644,6 +4007,12 @@ public final class ContactsContract {
* <td>{@link #DATA9}</td>
* <td></td>
* </tr>
+ * <tr>
+ * <td>String</td>
+ * <td>PHONETIC_NAME_STYLE</td>
+ * <td>{@link #DATA10}</td>
+ * <td></td>
+ * </tr>
* </table>
*/
public static final class Organization implements DataColumnsWithJoins, CommonColumns {
@@ -3701,6 +4070,13 @@ public final class ContactsContract {
public static final String OFFICE_LOCATION = DATA9;
/**
+ * The alphabet used for capturing the phonetic name.
+ * See {@link ContactsContract.PhoneticNameStyle}.
+ * @hide
+ */
+ public static final String PHONETIC_NAME_STYLE = DATA10;
+
+ /**
* Return the string resource that best describes the given
* {@link #TYPE}. Will always return a valid resource.
*/
@@ -4311,6 +4687,42 @@ public final class ContactsContract {
* The MIME type of a single group.
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/group";
+
+ public static EntityIterator newEntityIterator(Cursor cursor) {
+ return new EntityIteratorImpl(cursor);
+ }
+
+ private static class EntityIteratorImpl extends CursorEntityIterator {
+ public EntityIteratorImpl(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
+ // we expect the cursor is already at the row we need to read from
+ final ContentValues values = new ContentValues();
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, _ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, ACCOUNT_NAME);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, ACCOUNT_TYPE);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, DIRTY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, VERSION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SOURCE_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, RES_PACKAGE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, TITLE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, TITLE_RES);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, GROUP_VISIBLE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYNC1);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYNC2);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYNC3);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYNC4);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYSTEM_ID);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, DELETED);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, NOTES);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SHOULD_SYNC);
+ cursor.moveToNext();
+ return new Entity(values);
+ }
+ }
}
/**
@@ -4568,8 +4980,10 @@ public final class ContactsContract {
/**
* Extra used to specify pivot dialog location in screen coordinates.
+ * @deprecated Use {@link Intent#setSourceBounds(Rect)} instead.
* @hide
*/
+ @Deprecated
public static final String EXTRA_TARGET_RECT = "target_rect";
/**
@@ -4629,15 +5043,17 @@ public final class ContactsContract {
*/
public static void showQuickContact(Context context, View target, Uri lookupUri, int mode,
String[] excludeMimes) {
- // Find location and bounds of target view
- final int[] location = new int[2];
- target.getLocationOnScreen(location);
+ // Find location and bounds of target view, adjusting based on the
+ // assumed local density.
+ final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
+ final int[] pos = new int[2];
+ target.getLocationOnScreen(pos);
final Rect rect = new Rect();
- rect.left = location[0];
- rect.top = location[1];
- rect.right = rect.left + target.getWidth();
- rect.bottom = rect.top + target.getHeight();
+ rect.left = (int) (pos[0] * appScale + 0.5f);
+ rect.top = (int) (pos[1] * appScale + 0.5f);
+ rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f);
+ rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f);
// Trigger with obtained rectangle
showQuickContact(context, rect, lookupUri, mode, excludeMimes);
@@ -4654,8 +5070,11 @@ public final class ContactsContract {
* @param target Specific {@link Rect} that this dialog should be
* centered around, in screen coordinates. In particular, if
* the dialog has a "callout" arrow, it will be pointed and
- * centered around this {@link Rect}.
- * @param lookupUri A {@link ContactsContract.Contacts#CONTENT_LOOKUP_URI} style
+ * centered around this {@link Rect}. If you are running at a
+ * non-native density, you need to manually adjust using
+ * {@link DisplayMetrics#density} before calling.
+ * @param lookupUri A
+ * {@link ContactsContract.Contacts#CONTENT_LOOKUP_URI} style
* {@link Uri} that describes a specific contact to feature
* in this dialog.
* @param mode Any of {@link #MODE_SMALL}, {@link #MODE_MEDIUM}, or
@@ -4674,7 +5093,7 @@ public final class ContactsContract {
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
intent.setData(lookupUri);
- intent.putExtra(EXTRA_TARGET_RECT, target);
+ intent.setSourceBounds(target);
intent.putExtra(EXTRA_MODE, mode);
intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes);
context.startActivity(intent);
diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java
deleted file mode 100644
index 073ae6c..0000000
--- a/core/java/android/provider/Gmail.java
+++ /dev/null
@@ -1,2467 +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.provider;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentQueryMap;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.text.Html;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.text.style.CharacterStyle;
-import android.text.util.Regex;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A thin wrapper over the content resolver for accessing the gmail provider.
- *
- * @hide
- */
-public final class Gmail {
- // Set to true to enable extra debugging.
- private static final boolean DEBUG = false;
-
- public static final String GMAIL_AUTH_SERVICE = "mail";
- // These constants come from google3/java/com/google/caribou/backend/MailLabel.java.
- public static final String LABEL_SENT = "^f";
- public static final String LABEL_INBOX = "^i";
- public static final String LABEL_DRAFT = "^r";
- public static final String LABEL_UNREAD = "^u";
- public static final String LABEL_TRASH = "^k";
- public static final String LABEL_SPAM = "^s";
- public static final String LABEL_STARRED = "^t";
- public static final String LABEL_CHAT = "^b"; // 'b' for 'buzz'
- public static final String LABEL_VOICEMAIL = "^vm";
- public static final String LABEL_IGNORED = "^g";
- public static final String LABEL_ALL = "^all";
- // These constants (starting with "^^") are only used locally and are not understood by the
- // server.
- public static final String LABEL_VOICEMAIL_INBOX = "^^vmi";
- public static final String LABEL_CACHED = "^^cached";
- public static final String LABEL_OUTBOX = "^^out";
-
- public static final String AUTHORITY = "gmail-ls";
- private static final String TAG = "Gmail";
- private static final String AUTHORITY_PLUS_CONVERSATIONS =
- "content://" + AUTHORITY + "/conversations/";
- private static final String AUTHORITY_PLUS_LABELS =
- "content://" + AUTHORITY + "/labels/";
- private static final String AUTHORITY_PLUS_MESSAGES =
- "content://" + AUTHORITY + "/messages/";
- private static final String AUTHORITY_PLUS_SETTINGS =
- "content://" + AUTHORITY + "/settings/";
-
- public static final Uri BASE_URI = Uri.parse(
- "content://" + AUTHORITY);
- private static final Uri LABELS_URI =
- Uri.parse(AUTHORITY_PLUS_LABELS);
- private static final Uri CONVERSATIONS_URI =
- Uri.parse(AUTHORITY_PLUS_CONVERSATIONS);
- private static final Uri SETTINGS_URI =
- Uri.parse(AUTHORITY_PLUS_SETTINGS);
-
- /** Separates email addresses in strings in the database. */
- public static final String EMAIL_SEPARATOR = "\n";
- public static final Pattern EMAIL_SEPARATOR_PATTERN = Pattern.compile(EMAIL_SEPARATOR);
-
- /**
- * Space-separated lists have separators only between items.
- */
- private static final char SPACE_SEPARATOR = ' ';
- public static final Pattern SPACE_SEPARATOR_PATTERN = Pattern.compile(" ");
-
- /**
- * Comma-separated lists have separators between each item, before the first and after the last
- * item. The empty list is <tt>,</tt>.
- *
- * <p>This makes them easier to modify with SQL since it is not a special case to add or
- * remove the last item. Having a separator on each side of each value also makes it safe to use
- * SQL's REPLACE to remove an item from a string by using REPLACE(',value,', ',').
- *
- * <p>We could use the same separator for both lists but this makes it easier to remember which
- * kind of list one is dealing with.
- */
- private static final char COMMA_SEPARATOR = ',';
- public static final Pattern COMMA_SEPARATOR_PATTERN = Pattern.compile(",");
-
- /** Separates attachment info parts in strings in the database. */
- public static final String ATTACHMENT_INFO_SEPARATOR = "\n";
- public static final Pattern ATTACHMENT_INFO_SEPARATOR_PATTERN =
- Pattern.compile(ATTACHMENT_INFO_SEPARATOR);
-
- public static final Character SENDER_LIST_SEPARATOR = '\n';
- public static final String SENDER_LIST_TOKEN_ELIDED = "e";
- public static final String SENDER_LIST_TOKEN_NUM_MESSAGES = "n";
- public static final String SENDER_LIST_TOKEN_NUM_DRAFTS = "d";
- public static final String SENDER_LIST_TOKEN_LITERAL = "l";
- public static final String SENDER_LIST_TOKEN_SENDING = "s";
- public static final String SENDER_LIST_TOKEN_SEND_FAILED = "f";
-
- /** Used for finding status in a cursor's extras. */
- public static final String EXTRA_STATUS = "status";
-
- public static final String RESPOND_INPUT_COMMAND = "command";
- public static final String COMMAND_RETRY = "retry";
- public static final String COMMAND_ACTIVATE = "activate";
- public static final String COMMAND_SET_VISIBLE = "setVisible";
- public static final String SET_VISIBLE_PARAM_VISIBLE = "visible";
- public static final String RESPOND_OUTPUT_COMMAND_RESPONSE = "commandResponse";
- public static final String COMMAND_RESPONSE_OK = "ok";
- public static final String COMMAND_RESPONSE_UNKNOWN = "unknownCommand";
-
- public static final String INSERT_PARAM_ATTACHMENT_ORIGIN = "origin";
- public static final String INSERT_PARAM_ATTACHMENT_ORIGIN_EXTRAS = "originExtras";
-
- private static final Pattern NAME_ADDRESS_PATTERN = Pattern.compile("\"(.*)\"");
- private static final Pattern UNNAMED_ADDRESS_PATTERN = Pattern.compile("([^<]+)@");
-
- private static final Map<Integer, Integer> sPriorityToLength = Maps.newHashMap();
- public static final SimpleStringSplitter sSenderListSplitter =
- new SimpleStringSplitter(SENDER_LIST_SEPARATOR);
- public static String[] sSenderFragments = new String[8];
-
- /**
- * Returns the name in an address string
- * @param addressString such as &quot;bobby&quot; &lt;bob@example.com&gt;
- * @return returns the quoted name in the addressString, otherwise the username from the email
- * address
- */
- public static String getNameFromAddressString(String addressString) {
- Matcher namedAddressMatch = NAME_ADDRESS_PATTERN.matcher(addressString);
- if (namedAddressMatch.find()) {
- String name = namedAddressMatch.group(1);
- if (name.length() > 0) return name;
- addressString =
- addressString.substring(namedAddressMatch.end(), addressString.length());
- }
-
- Matcher unnamedAddressMatch = UNNAMED_ADDRESS_PATTERN.matcher(addressString);
- if (unnamedAddressMatch.find()) {
- return unnamedAddressMatch.group(1);
- }
-
- return addressString;
- }
-
- /**
- * Returns the email address in an address string
- * @param addressString such as &quot;bobby&quot; &lt;bob@example.com&gt;
- * @return returns the email address, such as bob@example.com from the example above
- */
- public static String getEmailFromAddressString(String addressString) {
- String result = addressString;
- Matcher match = Regex.EMAIL_ADDRESS_PATTERN.matcher(addressString);
- if (match.find()) {
- result = addressString.substring(match.start(), match.end());
- }
-
- return result;
- }
-
- /**
- * Returns whether the label is user-defined (versus system-defined labels such as inbox, whose
- * names start with "^").
- */
- public static boolean isLabelUserDefined(String label) {
- // TODO: label should never be empty so we should be able to say [label.charAt(0) != '^'].
- // However, it's a release week and I'm too scared to make that change.
- return !label.startsWith("^");
- }
-
- private static final Set<String> USER_SETTABLE_BUILTIN_LABELS = Sets.newHashSet(
- Gmail.LABEL_INBOX,
- Gmail.LABEL_UNREAD,
- Gmail.LABEL_TRASH,
- Gmail.LABEL_SPAM,
- Gmail.LABEL_STARRED,
- Gmail.LABEL_IGNORED);
-
- /**
- * Returns whether the label is user-settable. For example, labels such as LABEL_DRAFT should
- * only be set internally.
- */
- public static boolean isLabelUserSettable(String label) {
- return USER_SETTABLE_BUILTIN_LABELS.contains(label) || isLabelUserDefined(label);
- }
-
- /**
- * Returns the set of labels using the raw labels from a previous getRawLabels()
- * as input.
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public static Set<Long> getLabelIdsFromLabelIdsString(
- TextUtils.StringSplitter splitter) {
- Set<Long> labelIds = Sets.newHashSet();
- for (String labelIdString : splitter) {
- labelIds.add(Long.valueOf(labelIdString));
- }
- return labelIds;
- }
-
- /**
- * @deprecated remove when the activities stop using canonical names to identify labels
- */
- public static Set<String> getCanonicalNamesFromLabelIdsString(
- LabelMap labelMap, TextUtils.StringSplitter splitter) {
- Set<String> canonicalNames = Sets.newHashSet();
- for (long labelId : getLabelIdsFromLabelIdsString(splitter)) {
- final String canonicalName = labelMap.getCanonicalName(labelId);
- // We will sometimes see labels that the label map does not yet know about or that
- // do not have names yet.
- if (!TextUtils.isEmpty(canonicalName)) {
- canonicalNames.add(canonicalName);
- } else {
- Log.w(TAG, "getCanonicalNamesFromLabelIdsString skipping label id: " + labelId);
- }
- }
- return canonicalNames;
- }
-
- /**
- * @return a StringSplitter that is configured to split message label id strings
- */
- public static TextUtils.StringSplitter newMessageLabelIdsSplitter() {
- return new TextUtils.SimpleStringSplitter(SPACE_SEPARATOR);
- }
-
- /**
- * @return a StringSplitter that is configured to split conversation label id strings
- */
- public static TextUtils.StringSplitter newConversationLabelIdsSplitter() {
- return new CommaStringSplitter();
- }
-
- /**
- * A splitter for strings of the form described in the docs for COMMA_SEPARATOR.
- */
- private static class CommaStringSplitter extends TextUtils.SimpleStringSplitter {
-
- public CommaStringSplitter() {
- super(COMMA_SEPARATOR);
- }
-
- @Override
- public void setString(String string) {
- // The string should always be at least a single comma.
- super.setString(string.substring(1));
- }
- }
-
- /**
- * Creates a single string of the form that getLabelIdsFromLabelIdsString can split.
- */
- public static String getLabelIdsStringFromLabelIds(Set<Long> labelIds) {
- StringBuilder sb = new StringBuilder();
- sb.append(COMMA_SEPARATOR);
- for (Long labelId : labelIds) {
- sb.append(labelId);
- sb.append(COMMA_SEPARATOR);
- }
- return sb.toString();
- }
-
- public static final class ConversationColumns {
- public static final String ID = "_id";
- public static final String SUBJECT = "subject";
- public static final String SNIPPET = "snippet";
- public static final String FROM = "fromAddress";
- public static final String DATE = "date";
- public static final String PERSONAL_LEVEL = "personalLevel";
- /** A list of label names with a space after each one (including the last one). This makes
- * it easier remove individual labels from this list using SQL. */
- public static final String LABEL_IDS = "labelIds";
- public static final String NUM_MESSAGES = "numMessages";
- public static final String MAX_MESSAGE_ID = "maxMessageId";
- public static final String HAS_ATTACHMENTS = "hasAttachments";
- public static final String HAS_MESSAGES_WITH_ERRORS = "hasMessagesWithErrors";
- public static final String FORCE_ALL_UNREAD = "forceAllUnread";
-
- private ConversationColumns() {}
- }
-
- public static final class MessageColumns {
-
- public static final String ID = "_id";
- public static final String MESSAGE_ID = "messageId";
- public static final String CONVERSATION_ID = "conversation";
- public static final String SUBJECT = "subject";
- public static final String SNIPPET = "snippet";
- public static final String FROM = "fromAddress";
- public static final String TO = "toAddresses";
- public static final String CC = "ccAddresses";
- public static final String BCC = "bccAddresses";
- public static final String REPLY_TO = "replyToAddresses";
- public static final String DATE_SENT_MS = "dateSentMs";
- public static final String DATE_RECEIVED_MS = "dateReceivedMs";
- public static final String LIST_INFO = "listInfo";
- public static final String PERSONAL_LEVEL = "personalLevel";
- public static final String BODY = "body";
- public static final String EMBEDS_EXTERNAL_RESOURCES = "bodyEmbedsExternalResources";
- public static final String LABEL_IDS = "labelIds";
- public static final String JOINED_ATTACHMENT_INFOS = "joinedAttachmentInfos";
- public static final String ERROR = "error";
- // TODO: add a method for accessing this
- public static final String REF_MESSAGE_ID = "refMessageId";
-
- // Fake columns used only for saving or sending messages.
- public static final String FAKE_SAVE = "save";
- public static final String FAKE_REF_MESSAGE_ID = "refMessageId";
-
- private MessageColumns() {}
- }
-
- public static final class LabelColumns {
- public static final String CANONICAL_NAME = "canonicalName";
- public static final String NAME = "name";
- public static final String NUM_CONVERSATIONS = "numConversations";
- public static final String NUM_UNREAD_CONVERSATIONS =
- "numUnreadConversations";
-
- private LabelColumns() {}
- }
-
- public static final class SettingsColumns {
- public static final String LABELS_INCLUDED = "labelsIncluded";
- public static final String LABELS_PARTIAL = "labelsPartial";
- public static final String CONVERSATION_AGE_DAYS =
- "conversationAgeDays";
- public static final String MAX_ATTACHMENET_SIZE_MB =
- "maxAttachmentSize";
- }
-
- /**
- * These flags can be included as Selection Arguments when
- * querying the provider.
- */
- public static class SelectionArguments {
- private SelectionArguments() {
- // forbid instantiation
- }
-
- /**
- * Specifies that you do NOT wish the returned cursor to
- * become the Active Network Cursor. If you do not include
- * this flag as a selectionArg, the new cursor will become the
- * Active Network Cursor by default.
- */
- public static final String DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR =
- "SELECTION_ARGUMENT_DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR";
- }
-
- // These are the projections that we need when getting cursors from the
- // content provider.
- private static String[] CONVERSATION_PROJECTION = {
- ConversationColumns.ID,
- ConversationColumns.SUBJECT,
- ConversationColumns.SNIPPET,
- ConversationColumns.FROM,
- ConversationColumns.DATE,
- ConversationColumns.PERSONAL_LEVEL,
- ConversationColumns.LABEL_IDS,
- ConversationColumns.NUM_MESSAGES,
- ConversationColumns.MAX_MESSAGE_ID,
- ConversationColumns.HAS_ATTACHMENTS,
- ConversationColumns.HAS_MESSAGES_WITH_ERRORS,
- ConversationColumns.FORCE_ALL_UNREAD};
- private static String[] MESSAGE_PROJECTION = {
- MessageColumns.ID,
- MessageColumns.MESSAGE_ID,
- MessageColumns.CONVERSATION_ID,
- MessageColumns.SUBJECT,
- MessageColumns.SNIPPET,
- MessageColumns.FROM,
- MessageColumns.TO,
- MessageColumns.CC,
- MessageColumns.BCC,
- MessageColumns.REPLY_TO,
- MessageColumns.DATE_SENT_MS,
- MessageColumns.DATE_RECEIVED_MS,
- MessageColumns.LIST_INFO,
- MessageColumns.PERSONAL_LEVEL,
- MessageColumns.BODY,
- MessageColumns.EMBEDS_EXTERNAL_RESOURCES,
- MessageColumns.LABEL_IDS,
- MessageColumns.JOINED_ATTACHMENT_INFOS,
- MessageColumns.ERROR};
- private static String[] LABEL_PROJECTION = {
- BaseColumns._ID,
- LabelColumns.CANONICAL_NAME,
- LabelColumns.NAME,
- LabelColumns.NUM_CONVERSATIONS,
- LabelColumns.NUM_UNREAD_CONVERSATIONS};
- private static String[] SETTINGS_PROJECTION = {
- SettingsColumns.LABELS_INCLUDED,
- SettingsColumns.LABELS_PARTIAL,
- SettingsColumns.CONVERSATION_AGE_DAYS,
- SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
- };
-
- private ContentResolver mContentResolver;
-
- public Gmail(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
- }
-
- /**
- * Returns source if source is non-null. Returns the empty string otherwise.
- */
- private static String toNonnullString(String source) {
- if (source == null) {
- return "";
- } else {
- return source;
- }
- }
-
- /**
- * Behavior for a new cursor: should it become the Active Network
- * Cursor? This could potentially lead to bad behavior if someone
- * else is using the Active Network Cursor, since theirs will stop
- * being the Active Network Cursor.
- */
- public static enum BecomeActiveNetworkCursor {
- /**
- * The new cursor should become the one and only Active
- * Network Cursor. Any other cursor that might already be the
- * Active Network Cursor will cease to be so.
- */
- YES,
-
- /**
- * The new cursor should not become the Active Network
- * Cursor. Any other cursor that might already be the Active
- * Network Cursor will continue to be so.
- */
- NO
- }
-
- /**
- * Wraps a Cursor in a ConversationCursor
- *
- * @param account the account the cursor is associated with
- * @param cursor The Cursor to wrap
- * @return a new ConversationCursor
- */
- public ConversationCursor getConversationCursorForCursor(String account, Cursor cursor) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- return new ConversationCursor(this, account, cursor);
- }
-
- /**
- * Creates an array of SelectionArguments suitable for passing to the provider's query.
- * Currently this only handles one flag, but it could be expanded in the future.
- */
- private static String[] getSelectionArguments(
- BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- if (BecomeActiveNetworkCursor.NO == becomeActiveNetworkCursor) {
- return new String[] {SelectionArguments.DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR};
- } else {
- // Default behavior; no args required.
- return null;
- }
- }
-
- /**
- * Asynchronously gets a cursor over all conversations matching a query. The
- * query is in Gmail's query syntax. When the operation is complete the handler's
- * onQueryComplete() method is called with the resulting Cursor.
- *
- * @param account run the query on this account
- * @param handler An AsyncQueryHanlder that will be used to run the query
- * @param token The token to pass to startQuery, which will be passed back to onQueryComplete
- * @param query a query in Gmail's query syntax
- * @param becomeActiveNetworkCursor whether or not the returned
- * cursor should become the Active Network Cursor
- */
- public void runQueryForConversations(String account, AsyncQueryHandler handler, int token,
- String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
- handler.startQuery(token, null, Uri.withAppendedPath(CONVERSATIONS_URI, account),
- CONVERSATION_PROJECTION, query, selectionArgs, null);
- }
-
- /**
- * Synchronously gets a cursor over all conversations matching a query. The
- * query is in Gmail's query syntax.
- *
- * @param account run the query on this account
- * @param query a query in Gmail's query syntax
- * @param becomeActiveNetworkCursor whether or not the returned
- * cursor should become the Active Network Cursor
- */
- public ConversationCursor getConversationCursorForQuery(
- String account, String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
- Cursor cursor = mContentResolver.query(
- Uri.withAppendedPath(CONVERSATIONS_URI, account), CONVERSATION_PROJECTION,
- query, selectionArgs, null);
- return new ConversationCursor(this, account, cursor);
- }
-
- /**
- * Gets a message cursor over the single message with the given id.
- *
- * @param account get the cursor for messages in this account
- * @param messageId the id of the message
- * @return a cursor over the message
- */
- public MessageCursor getMessageCursorForMessageId(String account, long messageId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, null, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Gets a message cursor over the messages that match the query. Note that
- * this simply finds all of the messages that match and returns them. It
- * does not return all messages in conversations where any message matches.
- *
- * @param account get the cursor for messages in this account
- * @param query a query in GMail's query syntax. Currently only queries of
- * the form [label:<label>] are supported
- * @return a cursor over the messages
- */
- public MessageCursor getLocalMessageCursorForQuery(String account, String query) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
- Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, query, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Gets a cursor over all of the messages in a conversation.
- *
- * @param account get the cursor for messages in this account
- * @param conversationId the id of the converstion to fetch messages for
- * @return a cursor over messages in the conversation
- */
- public MessageCursor getMessageCursorForConversationId(String account, long conversationId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/messages");
- Cursor cursor = mContentResolver.query(
- uri, MESSAGE_PROJECTION, null, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Expunge the indicated message. One use of this is to discard drafts.
- *
- * @param account the account of the message id
- * @param messageId the id of the message to expunge
- */
- public void expungeMessage(String account, long messageId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- mContentResolver.delete(uri, null, null);
- }
-
- /**
- * Adds or removes the label on the conversation.
- *
- * @param account the account of the conversation
- * @param conversationId the conversation
- * @param maxServerMessageId the highest message id to whose labels should be changed. Note that
- * everywhere else in this file messageId means local message id but here you need to use a
- * server message id.
- * @param label the label to add or remove
- * @param add true to add the label, false to remove it
- */
- public void addOrRemoveLabelOnConversation(
- String account, long conversationId, long maxServerMessageId, String label,
- boolean add) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- if (add) {
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/labels");
- ContentValues values = new ContentValues();
- values.put(LabelColumns.CANONICAL_NAME, label);
- values.put(ConversationColumns.MAX_MESSAGE_ID, maxServerMessageId);
- mContentResolver.insert(uri, values);
- } else {
- String encodedLabel;
- try {
- encodedLabel = URLEncoder.encode(label, "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/"
- + conversationId + "/labels/" + encodedLabel);
- mContentResolver.delete(
- uri, ConversationColumns.MAX_MESSAGE_ID, new String[]{"" + maxServerMessageId});
- }
- }
-
- /**
- * Adds or removes the label on the message.
- *
- * @param contentResolver the content resolver.
- * @param account the account of the message
- * @param conversationId the conversation containing the message
- * @param messageId the id of the message to whose labels should be changed
- * @param label the label to add or remove
- * @param add true to add the label, false to remove it
- */
- public static void addOrRemoveLabelOnMessage(ContentResolver contentResolver, String account,
- long conversationId, long messageId, String label, boolean add) {
-
- // conversationId is unused but we want to start passing it whereever we pass a message id.
- if (add) {
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId + "/labels");
- ContentValues values = new ContentValues();
- values.put(LabelColumns.CANONICAL_NAME, label);
- contentResolver.insert(uri, values);
- } else {
- String encodedLabel;
- try {
- encodedLabel = URLEncoder.encode(label, "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId
- + "/labels/" + encodedLabel);
- contentResolver.delete(uri, null, null);
- }
- }
-
- /**
- * The mail provider will send an intent when certain changes happen in certain labels.
- * Currently those labels are inbox and voicemail.
- *
- * <p>The intent will have the action ACTION_PROVIDER_CHANGED and the extras mentioned below.
- * The data for the intent will be content://gmail-ls/unread/<name of label>.
- *
- * <p>The goal is to support the following user experience:<ul>
- * <li>When present the new mail indicator reports the number of unread conversations in the
- * inbox (or some other label).</li>
- * <li>When the user views the inbox the indicator is removed immediately. They do not have to
- * read all of the conversations.</li>
- * <li>If more mail arrives the indicator reappears and shows the total number of unread
- * conversations in the inbox.</li>
- * <li>If the user reads the new conversations on the web the indicator disappears on the
- * phone since there is no unread mail in the inbox that the user hasn't seen.</li>
- * <li>The phone should vibrate/etc when it transitions from having no unseen unread inbox
- * mail to having some.</li>
- */
-
- /** The account in which the change occurred. */
- static public final String PROVIDER_CHANGED_EXTRA_ACCOUNT = "account";
-
- /** The number of unread conversations matching the label. */
- static public final String PROVIDER_CHANGED_EXTRA_COUNT = "count";
-
- /** Whether to get the user's attention, perhaps by vibrating. */
- static public final String PROVIDER_CHANGED_EXTRA_GET_ATTENTION = "getAttention";
-
- /**
- * A label that is attached to all of the conversations being notified about. This enables the
- * receiver of a notification to get a list of matching conversations.
- */
- static public final String PROVIDER_CHANGED_EXTRA_TAG_LABEL = "tagLabel";
-
- /**
- * Settings for which conversations should be synced to the phone.
- * Conversations are synced if any message matches any of the following
- * criteria:
- *
- * <ul>
- * <li>the message has a label in the include set</li>
- * <li>the message is no older than conversationAgeDays and has a label in the partial set.
- * </li>
- * <li>also, pending changes on the server: the message has no user-controllable labels.</li>
- * </ul>
- *
- * <p>A user-controllable label is a user-defined label or star, inbox,
- * trash, spam, etc. LABEL_UNREAD is not considered user-controllable.
- */
- public static class Settings {
- public long conversationAgeDays;
- public long maxAttachmentSizeMb;
- public String[] labelsIncluded;
- public String[] labelsPartial;
- }
-
- /**
- * Returns the settings.
- * @param account the account whose setting should be retrieved
- */
- public Settings getSettings(String account) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Settings settings = new Settings();
- Cursor cursor = mContentResolver.query(
- Uri.withAppendedPath(SETTINGS_URI, account), SETTINGS_PROJECTION, null, null, null);
- cursor.moveToNext();
- settings.labelsIncluded = TextUtils.split(cursor.getString(0), SPACE_SEPARATOR_PATTERN);
- settings.labelsPartial = TextUtils.split(cursor.getString(1), SPACE_SEPARATOR_PATTERN);
- settings.conversationAgeDays = Long.parseLong(cursor.getString(2));
- settings.maxAttachmentSizeMb = Long.parseLong(cursor.getString(3));
- cursor.close();
- return settings;
- }
-
- /**
- * Sets the settings. A sync will be scheduled automatically.
- */
- public void setSettings(String account, Settings settings) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- ContentValues values = new ContentValues();
- values.put(
- SettingsColumns.LABELS_INCLUDED,
- TextUtils.join(" ", settings.labelsIncluded));
- values.put(
- SettingsColumns.LABELS_PARTIAL,
- TextUtils.join(" ", settings.labelsPartial));
- values.put(
- SettingsColumns.CONVERSATION_AGE_DAYS,
- settings.conversationAgeDays);
- values.put(
- SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
- settings.maxAttachmentSizeMb);
- mContentResolver.update(Uri.withAppendedPath(SETTINGS_URI, account), values, null, null);
- }
-
- /**
- * Uses sender instructions to build a formatted string.
- *
- * <p>Sender list instructions contain compact information about the sender list. Most work that
- * can be done without knowing how much room will be availble for the sender list is done when
- * creating the instructions.
- *
- * <p>The instructions string consists of tokens separated by SENDER_LIST_SEPARATOR. Here are
- * the tokens, one per line:<ul>
- * <li><tt>n</tt></li>
- * <li><em>int</em>, the number of non-draft messages in the conversation</li>
- * <li><tt>d</tt</li>
- * <li><em>int</em>, the number of drafts in the conversation</li>
- * <li><tt>l</tt></li>
- * <li><em>literal html to be included in the output</em></li>
- * <li><tt>s</tt> indicates that the message is sending (in the outbox without errors)</li>
- * <li><tt>f</tt> indicates that the message failed to send (in the outbox with errors)</li>
- * <li><em>for each message</em><ul>
- * <li><em>int</em>, 0 for read, 1 for unread</li>
- * <li><em>int</em>, the priority of the message. Zero is the most important</li>
- * <li><em>text</em>, the sender text or blank for messages from 'me'</li>
- * </ul></li>
- * <li><tt>e</tt> to indicate that one or more messages have been elided</li>
- *
- * <p>The instructions indicate how many messages and drafts are in the conversation and then
- * describe the most important messages in order, indicating the priority of each message and
- * whether the message is unread.
- *
- * @param instructions instructions as described above
- * @param sb the SpannableStringBuilder to append to
- * @param maxChars the number of characters available to display the text
- * @param unreadStyle the CharacterStyle for unread messages, or null
- * @param draftsStyle the CharacterStyle for draft messages, or null
- * @param sendingString the string to use when there are messages scheduled to be sent
- * @param sendFailedString the string to use when there are messages that mailed to send
- * @param meString the string to use for messages sent by this user
- * @param draftString the string to use for "Draft"
- * @param draftPluralString the string to use for "Drafts"
- */
- public static void getSenderSnippet(
- String instructions, SpannableStringBuilder sb, int maxChars,
- CharacterStyle unreadStyle,
- CharacterStyle draftsStyle,
- CharSequence meString, CharSequence draftString, CharSequence draftPluralString,
- CharSequence sendingString, CharSequence sendFailedString,
- boolean forceAllUnread, boolean forceAllRead) {
- assert !(forceAllUnread && forceAllRead);
- boolean unreadStatusIsForced = forceAllUnread || forceAllRead;
- boolean forcedUnreadStatus = forceAllUnread;
-
- // Measure each fragment. It's ok to iterate over the entire set of fragments because it is
- // never a long list, even if there are many senders.
- final Map<Integer, Integer> priorityToLength = sPriorityToLength;
- priorityToLength.clear();
-
- int maxFoundPriority = Integer.MIN_VALUE;
- int numMessages = 0;
- int numDrafts = 0;
- CharSequence draftsFragment = "";
- CharSequence sendingFragment = "";
- CharSequence sendFailedFragment = "";
-
- sSenderListSplitter.setString(instructions);
- int numFragments = 0;
- String[] fragments = sSenderFragments;
- int currentSize = fragments.length;
- while (sSenderListSplitter.hasNext()) {
- fragments[numFragments++] = sSenderListSplitter.next();
- if (numFragments == currentSize) {
- sSenderFragments = new String[2 * currentSize];
- System.arraycopy(fragments, 0, sSenderFragments, 0, currentSize);
- currentSize *= 2;
- fragments = sSenderFragments;
- }
- }
-
- for (int i = 0; i < numFragments;) {
- String fragment0 = fragments[i++];
- if ("".equals(fragment0)) {
- // This should be the final fragment.
- } else if (Gmail.SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
- // ignore
- } else if (Gmail.SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
- numMessages = Integer.valueOf(fragments[i++]);
- } else if (Gmail.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
- String numDraftsString = fragments[i++];
- numDrafts = Integer.parseInt(numDraftsString);
- draftsFragment = numDrafts == 1 ? draftString :
- draftPluralString + " (" + numDraftsString + ")";
- } else if (Gmail.SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) {
- sb.append(Html.fromHtml(fragments[i++]));
- return;
- } else if (Gmail.SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
- sendingFragment = sendingString;
- } else if (Gmail.SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
- sendFailedFragment = sendFailedString;
- } else {
- String priorityString = fragments[i++];
- CharSequence nameString = fragments[i++];
- if (nameString.length() == 0) nameString = meString;
- int priority = Integer.parseInt(priorityString);
- priorityToLength.put(priority, nameString.length());
- maxFoundPriority = Math.max(maxFoundPriority, priority);
- }
- }
- String numMessagesFragment =
- (numMessages != 0) ? " (" + Integer.toString(numMessages + numDrafts) + ")" : "";
-
- // Don't allocate fixedFragment unless we need it
- SpannableStringBuilder fixedFragment = null;
- int fixedFragmentLength = 0;
- if (draftsFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- fixedFragment.append(draftsFragment);
- if (draftsStyle != null) {
- fixedFragment.setSpan(
- CharacterStyle.wrap(draftsStyle),
- 0, fixedFragment.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- if (sendingFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- if (fixedFragment.length() != 0) fixedFragment.append(", ");
- fixedFragment.append(sendingFragment);
- }
- if (sendFailedFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- if (fixedFragment.length() != 0) fixedFragment.append(", ");
- fixedFragment.append(sendFailedFragment);
- }
-
- if (fixedFragment != null) {
- fixedFragmentLength = fixedFragment.length();
- }
-
- final boolean normalMessagesExist =
- numMessagesFragment.length() != 0 || maxFoundPriority != Integer.MIN_VALUE;
- String preFixedFragement = "";
- if (normalMessagesExist && fixedFragmentLength != 0) {
- preFixedFragement = ", ";
- }
- int maxPriorityToInclude = -1; // inclusive
- int numCharsUsed =
- numMessagesFragment.length() + preFixedFragement.length() + fixedFragmentLength;
- int numSendersUsed = 0;
- while (maxPriorityToInclude < maxFoundPriority) {
- if (priorityToLength.containsKey(maxPriorityToInclude + 1)) {
- int length = numCharsUsed + priorityToLength.get(maxPriorityToInclude + 1);
- if (numCharsUsed > 0) length += 2;
- // We must show at least two senders if they exist. If we don't have space for both
- // then we will truncate names.
- if (length > maxChars && numSendersUsed >= 2) {
- break;
- }
- numCharsUsed = length;
- numSendersUsed++;
- }
- maxPriorityToInclude++;
- }
-
- int numCharsToRemovePerWord = 0;
- if (numCharsUsed > maxChars) {
- numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed;
- }
-
- boolean elided = false;
- for (int i = 0; i < numFragments;) {
- String fragment0 = fragments[i++];
- if ("".equals(fragment0)) {
- // This should be the final fragment.
- } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
- elided = true;
- } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
- i++;
- } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
- i++;
- } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
- } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
- } else {
- final String unreadString = fragment0;
- final String priorityString = fragments[i++];
- String nameString = fragments[i++];
- if (nameString.length() == 0) nameString = meString.toString();
- if (numCharsToRemovePerWord != 0) {
- nameString = nameString.substring(
- 0, Math.max(nameString.length() - numCharsToRemovePerWord, 0));
- }
- final boolean unread = unreadStatusIsForced
- ? forcedUnreadStatus : Integer.parseInt(unreadString) != 0;
- final int priority = Integer.parseInt(priorityString);
- if (priority <= maxPriorityToInclude) {
- if (sb.length() != 0) {
- sb.append(elided ? " .. " : ", ");
- }
- elided = false;
- int pos = sb.length();
- sb.append(nameString);
- if (unread && unreadStyle != null) {
- sb.setSpan(CharacterStyle.wrap(unreadStyle),
- pos, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- } else {
- elided = true;
- }
- }
- }
- sb.append(numMessagesFragment);
- if (fixedFragmentLength != 0) {
- sb.append(preFixedFragement);
- sb.append(fixedFragment);
- }
- }
-
- /**
- * This is a cursor that only defines methods to move throught the results
- * and register to hear about changes. All access to the data is left to
- * subinterfaces.
- */
- public static class MailCursor extends ContentObserver {
-
- // A list of observers of this cursor.
- private Set<MailCursorObserver> mObservers;
-
- // Updated values are accumulated here before being written out if the
- // cursor is asked to persist the changes.
- private ContentValues mUpdateValues;
-
- protected Cursor mCursor;
- protected String mAccount;
-
- public Cursor getCursor() {
- return mCursor;
- }
-
- /**
- * Constructs the MailCursor given a regular cursor, registering as a
- * change observer of the cursor.
- * @param account the account the cursor is associated with
- * @param cursor the underlying cursor
- */
- protected MailCursor(String account, Cursor cursor) {
- super(new Handler());
- mObservers = new HashSet<MailCursorObserver>();
- mCursor = cursor;
- mAccount = account;
- if (mCursor != null) mCursor.registerContentObserver(this);
- }
-
- /**
- * Gets the account associated with this cursor.
- * @return the account.
- */
- public String getAccount() {
- return mAccount;
- }
-
- protected void checkThread() {
- // Turn this on when activity code no longer runs in the sync thread
- // after notifications of changes.
-// Thread currentThread = Thread.currentThread();
-// if (currentThread != mThread) {
-// throw new RuntimeException("Accessed from the wrong thread");
-// }
- }
-
- /**
- * Lazily constructs a map of update values to apply to the database
- * if requested. This map is cleared out when we move to a different
- * item in the result set.
- *
- * @return a map of values to be applied by an update.
- */
- protected ContentValues getUpdateValues() {
- if (mUpdateValues == null) {
- mUpdateValues = new ContentValues();
- }
- return mUpdateValues;
- }
-
- /**
- * Called whenever mCursor is changed to point to a different row.
- * Subclasses should override this if they need to clear out state
- * when this happens.
- *
- * Subclasses must call the inherited version if they override this.
- */
- protected void onCursorPositionChanged() {
- mUpdateValues = null;
- }
-
- // ********* MailCursor
-
- /**
- * Returns the numbers of rows in the cursor.
- *
- * @return the number of rows in the cursor.
- */
- final public int count() {
- if (mCursor != null) {
- return mCursor.getCount();
- } else {
- return 0;
- }
- }
-
- /**
- * @return the current position of this cursor, or -1 if this cursor
- * has not been initialized.
- */
- final public int position() {
- if (mCursor != null) {
- return mCursor.getPosition();
- } else {
- return -1;
- }
- }
-
- /**
- * Move the cursor to an absolute position. The valid
- * range of vaues is -1 &lt;= position &lt;= count.
- *
- * <p>This method will return true if the request destination was
- * reachable, otherwise it returns false.
- *
- * @param position the zero-based position to move to.
- * @return whether the requested move fully succeeded.
- */
- final public boolean moveTo(int position) {
- checkCursor();
- checkThread();
- boolean moved = mCursor.moveToPosition(position);
- if (moved) onCursorPositionChanged();
- return moved;
- }
-
- /**
- * Move the cursor to the next row.
- *
- * <p>This method will return false if the cursor is already past the
- * last entry in the result set.
- *
- * @return whether the move succeeded.
- */
- final public boolean next() {
- checkCursor();
- checkThread();
- boolean moved = mCursor.moveToNext();
- if (moved) onCursorPositionChanged();
- return moved;
- }
-
- /**
- * Release all resources and locks associated with the cursor. The
- * cursor will not be valid after this function is called.
- */
- final public void release() {
- if (mCursor != null) {
- mCursor.unregisterContentObserver(this);
- mCursor.deactivate();
- }
- }
-
- final public void registerContentObserver(ContentObserver observer) {
- mCursor.registerContentObserver(observer);
- }
-
- final public void unregisterContentObserver(ContentObserver observer) {
- mCursor.unregisterContentObserver(observer);
- }
-
- final public void registerDataSetObserver(DataSetObserver observer) {
- mCursor.registerDataSetObserver(observer);
- }
-
- final public void unregisterDataSetObserver(DataSetObserver observer) {
- mCursor.unregisterDataSetObserver(observer);
- }
-
- /**
- * Register an observer to hear about changes to the cursor.
- *
- * @param observer the observer to register
- */
- final public void registerObserver(MailCursorObserver observer) {
- mObservers.add(observer);
- }
-
- /**
- * Unregister an observer.
- *
- * @param observer the observer to unregister
- */
- final public void unregisterObserver(MailCursorObserver observer) {
- mObservers.remove(observer);
- }
-
- // ********* ContentObserver
-
- @Override
- final public boolean deliverSelfNotifications() {
- return false;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- if (DEBUG) {
- Log.d(TAG, "MailCursor is notifying " + mObservers.size() + " observers");
- }
- for (MailCursorObserver o: mObservers) {
- o.onCursorChanged(this);
- }
- }
-
- protected void checkCursor() {
- if (mCursor == null) {
- throw new IllegalStateException(
- "cannot read from an insertion cursor");
- }
- }
-
- /**
- * Returns the string value of the column, or "" if the value is null.
- */
- protected String getStringInColumn(int columnIndex) {
- checkCursor();
- return toNonnullString(mCursor.getString(columnIndex));
- }
- }
-
- /**
- * A MailCursor observer is notified of changes to the result set of a
- * cursor.
- */
- public interface MailCursorObserver {
-
- /**
- * Called when the result set of a cursor has changed.
- *
- * @param cursor the cursor whose result set has changed.
- */
- void onCursorChanged(MailCursor cursor);
- }
-
- /**
- * A cursor over labels.
- */
- public final class LabelCursor extends MailCursor {
-
- private int mNameIndex;
- private int mNumConversationsIndex;
- private int mNumUnreadConversationsIndex;
-
- private LabelCursor(String account, Cursor cursor) {
- super(account, cursor);
-
- mNameIndex = mCursor.getColumnIndexOrThrow(LabelColumns.CANONICAL_NAME);
- mNumConversationsIndex =
- mCursor.getColumnIndexOrThrow(LabelColumns.NUM_CONVERSATIONS);
- mNumUnreadConversationsIndex = mCursor.getColumnIndexOrThrow(
- LabelColumns.NUM_UNREAD_CONVERSATIONS);
- }
-
- /**
- * Gets the canonical name of the current label.
- *
- * @return the current label's name.
- */
- public String getName() {
- return getStringInColumn(mNameIndex);
- }
-
- /**
- * Gets the number of conversations with this label.
- *
- * @return the number of conversations with this label.
- */
- public int getNumConversations() {
- return mCursor.getInt(mNumConversationsIndex);
- }
-
- /**
- * Gets the number of unread conversations with this label.
- *
- * @return the number of unread conversations with this label.
- */
- public int getNumUnreadConversations() {
- return mCursor.getInt(mNumUnreadConversationsIndex);
- }
- }
-
- /**
- * This is a map of labels. TODO: make it observable.
- */
- public static final class LabelMap extends Observable {
- private final static ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
-
- private ContentQueryMap mQueryMap;
- private SortedSet<String> mSortedUserLabels;
- private Map<String, Long> mCanonicalNameToId;
-
- private long mLabelIdSent;
- private long mLabelIdInbox;
- private long mLabelIdDraft;
- private long mLabelIdUnread;
- private long mLabelIdTrash;
- private long mLabelIdSpam;
- private long mLabelIdStarred;
- private long mLabelIdChat;
- private long mLabelIdVoicemail;
- private long mLabelIdIgnored;
- private long mLabelIdVoicemailInbox;
- private long mLabelIdCached;
- private long mLabelIdOutbox;
-
- private boolean mLabelsSynced = false;
-
- public LabelMap(ContentResolver contentResolver, String account, boolean keepUpdated) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Cursor cursor = contentResolver.query(
- Uri.withAppendedPath(LABELS_URI, account), LABEL_PROJECTION, null, null, null);
- init(cursor, keepUpdated);
- }
-
- public LabelMap(Cursor cursor, boolean keepUpdated) {
- init(cursor, keepUpdated);
- }
-
- private void init(Cursor cursor, boolean keepUpdated) {
- mQueryMap = new ContentQueryMap(cursor, BaseColumns._ID, keepUpdated, null);
- mSortedUserLabels = new TreeSet<String>(java.text.Collator.getInstance());
- mCanonicalNameToId = Maps.newHashMap();
- updateDataStructures();
- mQueryMap.addObserver(new Observer() {
- public void update(Observable observable, Object data) {
- updateDataStructures();
- setChanged();
- notifyObservers();
- }
- });
- }
-
- /**
- * @return whether at least some labels have been synced.
- */
- public boolean labelsSynced() {
- return mLabelsSynced;
- }
-
- /**
- * Updates the data structures that are maintained separately from mQueryMap after the query
- * map has changed.
- */
- private void updateDataStructures() {
- mSortedUserLabels.clear();
- mCanonicalNameToId.clear();
- for (Map.Entry<String, ContentValues> row : mQueryMap.getRows().entrySet()) {
- long labelId = Long.valueOf(row.getKey());
- String canonicalName = row.getValue().getAsString(LabelColumns.CANONICAL_NAME);
- if (isLabelUserDefined(canonicalName)) {
- mSortedUserLabels.add(canonicalName);
- }
- mCanonicalNameToId.put(canonicalName, labelId);
-
- if (LABEL_SENT.equals(canonicalName)) {
- mLabelIdSent = labelId;
- } else if (LABEL_INBOX.equals(canonicalName)) {
- mLabelIdInbox = labelId;
- } else if (LABEL_DRAFT.equals(canonicalName)) {
- mLabelIdDraft = labelId;
- } else if (LABEL_UNREAD.equals(canonicalName)) {
- mLabelIdUnread = labelId;
- } else if (LABEL_TRASH.equals(canonicalName)) {
- mLabelIdTrash = labelId;
- } else if (LABEL_SPAM.equals(canonicalName)) {
- mLabelIdSpam = labelId;
- } else if (LABEL_STARRED.equals(canonicalName)) {
- mLabelIdStarred = labelId;
- } else if (LABEL_CHAT.equals(canonicalName)) {
- mLabelIdChat = labelId;
- } else if (LABEL_IGNORED.equals(canonicalName)) {
- mLabelIdIgnored = labelId;
- } else if (LABEL_VOICEMAIL.equals(canonicalName)) {
- mLabelIdVoicemail = labelId;
- } else if (LABEL_VOICEMAIL_INBOX.equals(canonicalName)) {
- mLabelIdVoicemailInbox = labelId;
- } else if (LABEL_CACHED.equals(canonicalName)) {
- mLabelIdCached = labelId;
- } else if (LABEL_OUTBOX.equals(canonicalName)) {
- mLabelIdOutbox = labelId;
- }
- mLabelsSynced = mLabelIdSent != 0
- && mLabelIdInbox != 0
- && mLabelIdDraft != 0
- && mLabelIdUnread != 0
- && mLabelIdTrash != 0
- && mLabelIdSpam != 0
- && mLabelIdStarred != 0
- && mLabelIdChat != 0
- && mLabelIdIgnored != 0
- && mLabelIdVoicemail != 0;
- }
- }
-
- public long getLabelIdSent() {
- checkLabelsSynced();
- return mLabelIdSent;
- }
-
- public long getLabelIdInbox() {
- checkLabelsSynced();
- return mLabelIdInbox;
- }
-
- public long getLabelIdDraft() {
- checkLabelsSynced();
- return mLabelIdDraft;
- }
-
- public long getLabelIdUnread() {
- checkLabelsSynced();
- return mLabelIdUnread;
- }
-
- public long getLabelIdTrash() {
- checkLabelsSynced();
- return mLabelIdTrash;
- }
-
- public long getLabelIdSpam() {
- checkLabelsSynced();
- return mLabelIdSpam;
- }
-
- public long getLabelIdStarred() {
- checkLabelsSynced();
- return mLabelIdStarred;
- }
-
- public long getLabelIdChat() {
- checkLabelsSynced();
- return mLabelIdChat;
- }
-
- public long getLabelIdIgnored() {
- checkLabelsSynced();
- return mLabelIdIgnored;
- }
-
- public long getLabelIdVoicemail() {
- checkLabelsSynced();
- return mLabelIdVoicemail;
- }
-
- public long getLabelIdVoicemailInbox() {
- checkLabelsSynced();
- return mLabelIdVoicemailInbox;
- }
-
- public long getLabelIdCached() {
- checkLabelsSynced();
- return mLabelIdCached;
- }
-
- public long getLabelIdOutbox() {
- checkLabelsSynced();
- return mLabelIdOutbox;
- }
-
- private void checkLabelsSynced() {
- if (!labelsSynced()) {
- throw new IllegalStateException("LabelMap not initalized");
- }
- }
-
- /** Returns the list of user-defined labels in alphabetical order. */
- public SortedSet<String> getSortedUserLabels() {
- return mSortedUserLabels;
- }
-
- private static final List<String> SORTED_USER_MEANINGFUL_SYSTEM_LABELS =
- Lists.newArrayList(
- LABEL_INBOX, LABEL_STARRED, LABEL_CHAT, LABEL_SENT,
- LABEL_OUTBOX, LABEL_DRAFT, LABEL_ALL,
- LABEL_SPAM, LABEL_TRASH);
-
-
- private static final Set<String> USER_MEANINGFUL_SYSTEM_LABELS_SET =
- Sets.newHashSet(
- SORTED_USER_MEANINGFUL_SYSTEM_LABELS.toArray(
- new String[]{}));
-
- public static List<String> getSortedUserMeaningfulSystemLabels() {
- return SORTED_USER_MEANINGFUL_SYSTEM_LABELS;
- }
-
- public static Set<String> getUserMeaningfulSystemLabelsSet() {
- return USER_MEANINGFUL_SYSTEM_LABELS_SET;
- }
-
- /**
- * If you are ever tempted to remove outbox or draft from this set make sure you have a
- * way to stop draft and outbox messages from getting purged before they are sent to the
- * server.
- */
- private static final Set<String> FORCED_INCLUDED_LABELS =
- Sets.newHashSet(LABEL_OUTBOX, LABEL_DRAFT);
-
- public static Set<String> getForcedIncludedLabels() {
- return FORCED_INCLUDED_LABELS;
- }
-
- private static final Set<String> FORCED_INCLUDED_OR_PARTIAL_LABELS =
- Sets.newHashSet(LABEL_INBOX);
-
- public static Set<String> getForcedIncludedOrPartialLabels() {
- return FORCED_INCLUDED_OR_PARTIAL_LABELS;
- }
-
- private static final Set<String> FORCED_UNSYNCED_LABELS =
- Sets.newHashSet(LABEL_ALL, LABEL_CHAT, LABEL_SPAM, LABEL_TRASH);
-
- public static Set<String> getForcedUnsyncedLabels() {
- return FORCED_UNSYNCED_LABELS;
- }
-
- /**
- * Returns the number of conversation with a given label.
- * @deprecated Use {@link #getLabelId} instead.
- */
- @Deprecated
- public int getNumConversations(String label) {
- return getNumConversations(getLabelId(label));
- }
-
- /** Returns the number of conversation with a given label. */
- public int getNumConversations(long labelId) {
- return getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_CONVERSATIONS);
- }
-
- /**
- * Returns the number of unread conversation with a given label.
- * @deprecated Use {@link #getLabelId} instead.
- */
- @Deprecated
- public int getNumUnreadConversations(String label) {
- return getNumUnreadConversations(getLabelId(label));
- }
-
- /** Returns the number of unread conversation with a given label. */
- public int getNumUnreadConversations(long labelId) {
- Integer unreadConversations =
- getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_UNREAD_CONVERSATIONS);
- // There seems to be a race condition here that can get the label maps into a bad
- // state and lose state on a particular label.
- int result = 0;
- if (unreadConversations != null) {
- result = unreadConversations < 0 ? 0 : unreadConversations;
- }
-
- return result;
- }
-
- /**
- * @return the canonical name for a label
- */
- public String getCanonicalName(long labelId) {
- return getLabelIdValues(labelId).getAsString(LabelColumns.CANONICAL_NAME);
- }
-
- /**
- * @return the human name for a label
- */
- public String getName(long labelId) {
- return getLabelIdValues(labelId).getAsString(LabelColumns.NAME);
- }
-
- /**
- * @return whether a given label is known
- */
- public boolean hasLabel(long labelId) {
- return mQueryMap.getRows().containsKey(Long.toString(labelId));
- }
-
- /**
- * @return returns the id of a label given the canonical name
- * @deprecated this is only needed because most of the UI uses label names instead of ids
- */
- public long getLabelId(String canonicalName) {
- if (mCanonicalNameToId.containsKey(canonicalName)) {
- return mCanonicalNameToId.get(canonicalName);
- } else {
- throw new IllegalArgumentException("Unknown canonical name: " + canonicalName);
- }
- }
-
- private ContentValues getLabelIdValues(long labelId) {
- final ContentValues values = mQueryMap.getValues(Long.toString(labelId));
- if (values != null) {
- return values;
- } else {
- return EMPTY_CONTENT_VALUES;
- }
- }
-
- /** Force the map to requery. This should not be necessary outside tests. */
- public void requery() {
- mQueryMap.requery();
- }
-
- public void close() {
- mQueryMap.close();
- }
- }
-
- private Map<String, Gmail.LabelMap> mLabelMaps = Maps.newHashMap();
-
- public LabelMap getLabelMap(String account) {
- Gmail.LabelMap labelMap = mLabelMaps.get(account);
- if (labelMap == null) {
- labelMap = new Gmail.LabelMap(mContentResolver, account, true /* keepUpdated */);
- mLabelMaps.put(account, labelMap);
- }
- return labelMap;
- }
-
- public enum PersonalLevel {
- NOT_TO_ME(0),
- TO_ME_AND_OTHERS(1),
- ONLY_TO_ME(2);
-
- private int mLevel;
-
- PersonalLevel(int level) {
- mLevel = level;
- }
-
- public int toInt() {
- return mLevel;
- }
-
- public static PersonalLevel fromInt(int level) {
- switch (level) {
- case 0: return NOT_TO_ME;
- case 1: return TO_ME_AND_OTHERS;
- case 2: return ONLY_TO_ME;
- default:
- throw new IllegalArgumentException(
- level + " is not a personal level");
- }
- }
- }
-
- /**
- * Indicates a version of an attachment.
- */
- public enum AttachmentRendition {
- /**
- * The full version of an attachment if it can be handled on the device, otherwise the
- * preview.
- */
- BEST,
-
- /** A smaller or simpler version of the attachment, such as a scaled-down image or an HTML
- * version of a document. Not always available.
- */
- SIMPLE,
- }
-
- /**
- * The columns that can be requested when querying an attachment's download URI. See
- * getAttachmentDownloadUri.
- */
- public static final class AttachmentColumns implements BaseColumns {
-
- /** Contains a STATUS value from {@link android.provider.Downloads} */
- public static final String STATUS = "status";
-
- /**
- * The name of the file to open (with ContentProvider.open). If this is empty then continue
- * to use the attachment's URI.
- *
- * TODO: I'm not sure that we need this. See the note in CL 66853-p9.
- */
- public static final String FILENAME = "filename";
- }
-
- /**
- * We track where an attachment came from so that we know how to download it and include it
- * in new messages.
- */
- public enum AttachmentOrigin {
- /** Extras are "<conversationId>-<messageId>-<partId>". */
- SERVER_ATTACHMENT,
- /** Extras are "<path>". */
- LOCAL_FILE;
-
- private static final String SERVER_EXTRAS_SEPARATOR = "_";
-
- public static String serverExtras(
- long conversationId, long messageId, String partId) {
- return conversationId + SERVER_EXTRAS_SEPARATOR
- + messageId + SERVER_EXTRAS_SEPARATOR + partId;
- }
-
- /**
- * @param extras extras as returned by serverExtras
- * @return an array of conversationId, messageId, partId (all as strings)
- */
- public static String[] splitServerExtras(String extras) {
- return TextUtils.split(extras, SERVER_EXTRAS_SEPARATOR);
- }
-
- public static String localFileExtras(Uri path) {
- return path.toString();
- }
- }
-
- public static final class Attachment {
- /** Identifies the attachment uniquely when combined wih a message id.*/
- public String partId;
-
- /** The intended filename of the attachment.*/
- public String name;
-
- /** The native content type.*/
- public String contentType;
-
- /** The size of the attachment in its native form.*/
- public int size;
-
- /**
- * The content type of the simple version of the attachment. Blank if no simple version is
- * available.
- */
- public String simpleContentType;
-
- public AttachmentOrigin origin;
-
- public String originExtras;
-
- public String toJoinedString() {
- return TextUtils.join(
- "|", Lists.newArrayList(partId == null ? "" : partId,
- name.replace("|", ""), contentType,
- size, simpleContentType,
- origin.toString(), originExtras));
- }
-
- public static Attachment parseJoinedString(String joinedString) {
- String[] fragments = TextUtils.split(joinedString, "\\|");
- int i = 0;
- Attachment attachment = new Attachment();
- attachment.partId = fragments[i++];
- if (TextUtils.isEmpty(attachment.partId)) {
- attachment.partId = null;
- }
- attachment.name = fragments[i++];
- attachment.contentType = fragments[i++];
- attachment.size = Integer.parseInt(fragments[i++]);
- attachment.simpleContentType = fragments[i++];
- attachment.origin = AttachmentOrigin.valueOf(fragments[i++]);
- attachment.originExtras = fragments[i++];
- return attachment;
- }
- }
-
- /**
- * Any given attachment can come in two different renditions (see
- * {@link android.provider.Gmail.AttachmentRendition}) and can be saved to the sd card or to a
- * cache. The gmail provider automatically syncs some attachments to the cache. Other
- * attachments can be downloaded on demand. Attachments in the cache will be purged as needed to
- * save space. Attachments on the SD card must be managed by the user or other software.
- *
- * @param account which account to use
- * @param messageId the id of the mesage with the attachment
- * @param attachment the attachment
- * @param rendition the desired rendition
- * @param saveToSd whether the attachment should be saved to (or loaded from) the sd card or
- * @return the URI to ask the content provider to open in order to open an attachment.
- */
- public static Uri getAttachmentUri(
- String account, long messageId, Attachment attachment,
- AttachmentRendition rendition, boolean saveToSd) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- if (attachment.origin == AttachmentOrigin.LOCAL_FILE) {
- return Uri.parse(attachment.originExtras);
- } else {
- return Uri.parse(
- AUTHORITY_PLUS_MESSAGES).buildUpon()
- .appendPath(account).appendPath(Long.toString(messageId))
- .appendPath("attachments").appendPath(attachment.partId)
- .appendPath(rendition.toString())
- .appendPath(Boolean.toString(saveToSd))
- .build();
- }
- }
-
- /**
- * Return the URI to query in order to find out whether an attachment is downloaded.
- *
- * <p>Querying this will also start a download if necessary. The cursor returned by querying
- * this URI can contain the columns in {@link android.provider.Gmail.AttachmentColumns}.
- *
- * <p>Deleting this URI will cancel the download if it was not started automatically by the
- * provider. It will also remove bookkeeping for saveToSd downloads.
- *
- * @param attachmentUri the attachment URI as returned by getAttachmentUri. The URI's authority
- * Gmail.AUTHORITY. If it is not then you should open the file directly.
- */
- public static Uri getAttachmentDownloadUri(Uri attachmentUri) {
- if (!"content".equals(attachmentUri.getScheme())) {
- throw new IllegalArgumentException("Uri's scheme must be 'content': " + attachmentUri);
- }
- return attachmentUri.buildUpon().appendPath("download").build();
- }
-
- public enum CursorStatus {
- LOADED,
- LOADING,
- ERROR, // A network error occurred.
- }
-
- /**
- * A cursor over messages.
- */
- public static final class MessageCursor extends MailCursor {
-
- private LabelMap mLabelMap;
-
- private ContentResolver mContentResolver;
-
- /**
- * Only valid if mCursor == null, in which case we are inserting a new
- * message.
- */
- long mInReplyToLocalMessageId;
- boolean mPreserveAttachments;
-
- private int mIdIndex;
- private int mConversationIdIndex;
- private int mSubjectIndex;
- private int mSnippetIndex;
- private int mFromIndex;
- private int mToIndex;
- private int mCcIndex;
- private int mBccIndex;
- private int mReplyToIndex;
- private int mDateSentMsIndex;
- private int mDateReceivedMsIndex;
- private int mListInfoIndex;
- private int mPersonalLevelIndex;
- private int mBodyIndex;
- private int mBodyEmbedsExternalResourcesIndex;
- private int mLabelIdsIndex;
- private int mJoinedAttachmentInfosIndex;
- private int mErrorIndex;
-
- private TextUtils.StringSplitter mLabelIdsSplitter = newMessageLabelIdsSplitter();
-
- public MessageCursor(Gmail gmail, ContentResolver cr, String account, Cursor cursor) {
- super(account, cursor);
- mLabelMap = gmail.getLabelMap(account);
- if (cursor == null) {
- throw new IllegalArgumentException(
- "null cursor passed to MessageCursor()");
- }
-
- mContentResolver = cr;
-
- mIdIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ID);
- mConversationIdIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.CONVERSATION_ID);
- mSubjectIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SUBJECT);
- mSnippetIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SNIPPET);
- mFromIndex = mCursor.getColumnIndexOrThrow(MessageColumns.FROM);
- mToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.TO);
- mCcIndex = mCursor.getColumnIndexOrThrow(MessageColumns.CC);
- mBccIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BCC);
- mReplyToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.REPLY_TO);
- mDateSentMsIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.DATE_SENT_MS);
- mDateReceivedMsIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.DATE_RECEIVED_MS);
- mListInfoIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LIST_INFO);
- mPersonalLevelIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.PERSONAL_LEVEL);
- mBodyIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BODY);
- mBodyEmbedsExternalResourcesIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.EMBEDS_EXTERNAL_RESOURCES);
- mLabelIdsIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LABEL_IDS);
- mJoinedAttachmentInfosIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.JOINED_ATTACHMENT_INFOS);
- mErrorIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ERROR);
-
- mInReplyToLocalMessageId = 0;
- mPreserveAttachments = false;
- }
-
- protected MessageCursor(ContentResolver cr, String account, long inReplyToMessageId,
- boolean preserveAttachments) {
- super(account, null);
- mContentResolver = cr;
- mInReplyToLocalMessageId = inReplyToMessageId;
- mPreserveAttachments = preserveAttachments;
- }
-
- @Override
- protected void onCursorPositionChanged() {
- super.onCursorPositionChanged();
- }
-
- public CursorStatus getStatus() {
- Bundle extras = mCursor.getExtras();
- String stringStatus = extras.getString(EXTRA_STATUS);
- return CursorStatus.valueOf(stringStatus);
- }
-
- /** Retry a network request after errors. */
- public void retry() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Gets the message id of the current message. Note that this is an
- * immutable local message (not, for example, GMail's message id, which
- * is immutable).
- *
- * @return the message's id
- */
- public long getMessageId() {
- checkCursor();
- return mCursor.getLong(mIdIndex);
- }
-
- /**
- * Gets the message's conversation id. This must be immutable. (For
- * example, with GMail this should be the original conversation id
- * rather than the default notion of converation id.)
- *
- * @return the message's conversation id
- */
- public long getConversationId() {
- checkCursor();
- return mCursor.getLong(mConversationIdIndex);
- }
-
- /**
- * Gets the message's subject.
- *
- * @return the message's subject
- */
- public String getSubject() {
- return getStringInColumn(mSubjectIndex);
- }
-
- /**
- * Gets the message's snippet (the short piece of the body). The snippet
- * is generated from the body and cannot be set directly.
- *
- * @return the message's snippet
- */
- public String getSnippet() {
- return getStringInColumn(mSnippetIndex);
- }
-
- /**
- * Gets the message's from address.
- *
- * @return the message's from address
- */
- public String getFromAddress() {
- return getStringInColumn(mFromIndex);
- }
-
- /**
- * Returns the addresses for the key, if it has been updated, or index otherwise.
- */
- private String[] getAddresses(String key, int index) {
- ContentValues updated = getUpdateValues();
- String addresses;
- if (updated.containsKey(key)) {
- addresses = (String)getUpdateValues().get(key);
- } else {
- addresses = getStringInColumn(index);
- }
-
- return TextUtils.split(addresses, EMAIL_SEPARATOR_PATTERN);
- }
-
- /**
- * Gets the message's to addresses.
- * @return the message's to addresses
- */
- public String[] getToAddresses() {
- return getAddresses(MessageColumns.TO, mToIndex);
- }
-
- /**
- * Gets the message's cc addresses.
- * @return the message's cc addresses
- */
- public String[] getCcAddresses() {
- return getAddresses(MessageColumns.CC, mCcIndex);
- }
-
- /**
- * Gets the message's bcc addresses.
- * @return the message's bcc addresses
- */
- public String[] getBccAddresses() {
- return getAddresses(MessageColumns.BCC, mBccIndex);
- }
-
- /**
- * Gets the message's replyTo address.
- *
- * @return the message's replyTo address
- */
- public String[] getReplyToAddress() {
- return TextUtils.split(getStringInColumn(mReplyToIndex), EMAIL_SEPARATOR_PATTERN);
- }
-
- public long getDateSentMs() {
- checkCursor();
- return mCursor.getLong(mDateSentMsIndex);
- }
-
- public long getDateReceivedMs() {
- checkCursor();
- return mCursor.getLong(mDateReceivedMsIndex);
- }
-
- public String getListInfo() {
- return getStringInColumn(mListInfoIndex);
- }
-
- public PersonalLevel getPersonalLevel() {
- checkCursor();
- int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
- return PersonalLevel.fromInt(personalLevelInt);
- }
-
- /**
- * @deprecated Always returns true.
- */
- @Deprecated
- public boolean getExpanded() {
- return true;
- }
-
- /**
- * Gets the message's body.
- *
- * @return the message's body
- */
- public String getBody() {
- return getStringInColumn(mBodyIndex);
- }
-
- /**
- * @return whether the message's body contains embedded references to external resources. In
- * that case the resources should only be displayed if the user explicitly asks for them to
- * be
- */
- public boolean getBodyEmbedsExternalResources() {
- checkCursor();
- return mCursor.getInt(mBodyEmbedsExternalResourcesIndex) != 0;
- }
-
- /**
- * @return a copy of the set of label ids
- */
- public Set<Long> getLabelIds() {
- String labelNames = mCursor.getString(mLabelIdsIndex);
- mLabelIdsSplitter.setString(labelNames);
- return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
- }
-
- /**
- * @return a joined string of labels separated by spaces.
- */
- public String getRawLabelIds() {
- return mCursor.getString(mLabelIdsIndex);
- }
-
- /**
- * Adds a label to a message (if add is true) or removes it (if add is
- * false).
- *
- * @param label the label to add or remove
- * @param add whether to add or remove the label
- */
- public void addOrRemoveLabel(String label, boolean add) {
- addOrRemoveLabelOnMessage(mContentResolver, mAccount, getConversationId(),
- getMessageId(), label, add);
- }
-
- public ArrayList<Attachment> getAttachmentInfos() {
- ArrayList<Attachment> attachments = Lists.newArrayList();
-
- String joinedAttachmentInfos = mCursor.getString(mJoinedAttachmentInfosIndex);
- if (joinedAttachmentInfos != null) {
- for (String joinedAttachmentInfo :
- TextUtils.split(joinedAttachmentInfos, ATTACHMENT_INFO_SEPARATOR_PATTERN)) {
-
- Attachment attachment = Attachment.parseJoinedString(joinedAttachmentInfo);
- attachments.add(attachment);
- }
- }
- return attachments;
- }
-
- /**
- * @return the error text for the message. Error text gets set if the server rejects a
- * message that we try to save or send. If there is error text then the message is no longer
- * scheduled to be saved or sent. Calling save() or send() will clear any error as well as
- * scheduling another atempt to save or send the message.
- */
- public String getErrorText() {
- return mCursor.getString(mErrorIndex);
- }
- }
-
- /**
- * A helper class for creating or updating messags. Use the putXxx methods to provide initial or
- * new values for the message. Then save or send the message. To save or send an existing
- * message without making other changes to it simply provide an emty ContentValues.
- */
- public static class MessageModification {
-
- /**
- * Sets the message's subject. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param subject the new subject
- */
- public static void putSubject(ContentValues values, String subject) {
- values.put(MessageColumns.SUBJECT, subject);
- }
-
- /**
- * Sets the message's to address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param toAddresses the new to addresses
- */
- public static void putToAddresses(ContentValues values, String[] toAddresses) {
- values.put(MessageColumns.TO, TextUtils.join(EMAIL_SEPARATOR, toAddresses));
- }
-
- /**
- * Sets the message's cc address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param ccAddresses the new cc addresses
- */
- public static void putCcAddresses(ContentValues values, String[] ccAddresses) {
- values.put(MessageColumns.CC, TextUtils.join(EMAIL_SEPARATOR, ccAddresses));
- }
-
- /**
- * Sets the message's bcc address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param bccAddresses the new bcc addresses
- */
- public static void putBccAddresses(ContentValues values, String[] bccAddresses) {
- values.put(MessageColumns.BCC, TextUtils.join(EMAIL_SEPARATOR, bccAddresses));
- }
-
- /**
- * Saves a new body for the message. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param body the new body of the message
- */
- public static void putBody(ContentValues values, String body) {
- values.put(MessageColumns.BODY, body);
- }
-
- /**
- * Sets the attachments on a message. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param attachments
- */
- public static void putAttachments(ContentValues values, List<Attachment> attachments) {
- values.put(
- MessageColumns.JOINED_ATTACHMENT_INFOS, joinedAttachmentsString(attachments));
- }
-
- /**
- * Create a new message and save it as a draft or send it.
- *
- * @param contentResolver the content resolver to use
- * @param account the account to use
- * @param values the values for the new message
- * @param refMessageId the message that is being replied to or forwarded
- * @param save whether to save or send the message
- * @return the id of the new message
- */
- public static long sendOrSaveNewMessage(
- ContentResolver contentResolver, String account,
- ContentValues values, long refMessageId, boolean save) {
- values.put(MessageColumns.FAKE_SAVE, save);
- values.put(MessageColumns.FAKE_REF_MESSAGE_ID, refMessageId);
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
- Uri result = contentResolver.insert(uri, values);
- return ContentUris.parseId(result);
- }
-
- /**
- * Update an existing draft and save it as a new draft or send it.
- *
- * @param contentResolver the content resolver to use
- * @param account the account to use
- * @param messageId the id of the message to update
- * @param updateValues the values to change. Unspecified fields will not be altered
- * @param save whether to resave the message as a draft or send it
- */
- public static void sendOrSaveExistingMessage(
- ContentResolver contentResolver, String account, long messageId,
- ContentValues updateValues, boolean save) {
- updateValues.put(MessageColumns.FAKE_SAVE, save);
- updateValues.put(MessageColumns.FAKE_REF_MESSAGE_ID, 0);
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- contentResolver.update(uri, updateValues, null, null);
- }
-
- /**
- * The string produced here is parsed by Gmail.MessageCursor#getAttachmentInfos.
- */
- public static String joinedAttachmentsString(List<Gmail.Attachment> attachments) {
- StringBuilder attachmentsSb = new StringBuilder();
- for (Gmail.Attachment attachment : attachments) {
- if (attachmentsSb.length() != 0) {
- attachmentsSb.append(Gmail.ATTACHMENT_INFO_SEPARATOR);
- }
- attachmentsSb.append(attachment.toJoinedString());
- }
- return attachmentsSb.toString();
- }
-
- }
-
- /**
- * A cursor over conversations.
- *
- * "Conversation" refers to the information needed to populate a list of
- * conversations, not all of the messages in a conversation.
- */
- public static final class ConversationCursor extends MailCursor {
-
- private LabelMap mLabelMap;
-
- private int mConversationIdIndex;
- private int mSubjectIndex;
- private int mSnippetIndex;
- private int mFromIndex;
- private int mDateIndex;
- private int mPersonalLevelIndex;
- private int mLabelIdsIndex;
- private int mNumMessagesIndex;
- private int mMaxMessageIdIndex;
- private int mHasAttachmentsIndex;
- private int mHasMessagesWithErrorsIndex;
- private int mForceAllUnreadIndex;
-
- private TextUtils.StringSplitter mLabelIdsSplitter = newConversationLabelIdsSplitter();
-
- private ConversationCursor(Gmail gmail, String account, Cursor cursor) {
- super(account, cursor);
- mLabelMap = gmail.getLabelMap(account);
-
- mConversationIdIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.ID);
- mSubjectIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SUBJECT);
- mSnippetIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SNIPPET);
- mFromIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.FROM);
- mDateIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.DATE);
- mPersonalLevelIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.PERSONAL_LEVEL);
- mLabelIdsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.LABEL_IDS);
- mNumMessagesIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.NUM_MESSAGES);
- mMaxMessageIdIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.MAX_MESSAGE_ID);
- mHasAttachmentsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_ATTACHMENTS);
- mHasMessagesWithErrorsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_MESSAGES_WITH_ERRORS);
- mForceAllUnreadIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.FORCE_ALL_UNREAD);
- }
-
- @Override
- protected void onCursorPositionChanged() {
- super.onCursorPositionChanged();
- }
-
- public CursorStatus getStatus() {
- Bundle extras = mCursor.getExtras();
- String stringStatus = extras.getString(EXTRA_STATUS);
- return CursorStatus.valueOf(stringStatus);
- }
-
- /** Retry a network request after errors. */
- public void retry() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * When a conversation cursor is created it becomes the active network cursor, which means
- * that it will fetch results from the network if it needs to in order to show all mail that
- * matches its query. If you later want to requery an older cursor and would like that
- * cursor to be the active cursor you need to call this method before requerying.
- */
- public void becomeActiveNetworkCursor() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_ACTIVATE);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Tells the cursor whether its contents are visible to the user. The cursor will
- * automatically broadcast intents to remove any matching new-mail notifications when this
- * cursor's results become visible and, if they are visible, when the cursor is requeried.
- *
- * Note that contents shown in an activity that is resumed but not focused
- * (onWindowFocusChanged/hasWindowFocus) then results shown in that activity do not count
- * as visible. (This happens when the activity is behind the lock screen or a dialog.)
- *
- * @param visible whether the contents of this cursor are visible to the user.
- */
- public void setContentsVisibleToUser(boolean visible) {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_SET_VISIBLE);
- input.putBoolean(SET_VISIBLE_PARAM_VISIBLE, visible);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Gets the conversation id. This is immutable. (The server calls it the original
- * conversation id.)
- *
- * @return the conversation id
- */
- public long getConversationId() {
- return mCursor.getLong(mConversationIdIndex);
- }
-
- /**
- * Returns the instructions for building from snippets. Pass this to getFromSnippetHtml
- * in order to actually build the snippets.
- * @return snippet instructions for use by getFromSnippetHtml()
- */
- public String getFromSnippetInstructions() {
- return getStringInColumn(mFromIndex);
- }
-
- /**
- * Gets the conversation's subject.
- *
- * @return the subject
- */
- public String getSubject() {
- return getStringInColumn(mSubjectIndex);
- }
-
- /**
- * Gets the conversation's snippet.
- *
- * @return the snippet
- */
- public String getSnippet() {
- return getStringInColumn(mSnippetIndex);
- }
-
- /**
- * Get's the conversation's personal level.
- *
- * @return the personal level.
- */
- public PersonalLevel getPersonalLevel() {
- int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
- return PersonalLevel.fromInt(personalLevelInt);
- }
-
- /**
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- * @deprecated use getLabelIds
- */
- public Set<String> getLabels() {
- return getLabels(getRawLabelIds(), mLabelMap);
- }
-
- /**
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public Set<Long> getLabelIds() {
- mLabelIdsSplitter.setString(getRawLabelIds());
- return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
- }
-
- /**
- * Returns the set of labels using the raw labels from a previous getRawLabels()
- * as input.
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public Set<String> getLabels(String rawLabelIds, LabelMap labelMap) {
- mLabelIdsSplitter.setString(rawLabelIds);
- return getCanonicalNamesFromLabelIdsString(labelMap, mLabelIdsSplitter);
- }
-
- /**
- * @return a joined string of labels separated by spaces. Use
- * getLabels(rawLabels) to convert this to a Set of labels.
- */
- public String getRawLabelIds() {
- return mCursor.getString(mLabelIdsIndex);
- }
-
- /**
- * @return the number of messages in the conversation
- */
- public int getNumMessages() {
- return mCursor.getInt(mNumMessagesIndex);
- }
-
- /**
- * @return the max message id in the conversation
- */
- public long getMaxServerMessageId() {
- return mCursor.getLong(mMaxMessageIdIndex);
- }
-
- public long getDateMs() {
- return mCursor.getLong(mDateIndex);
- }
-
- public boolean hasAttachments() {
- return mCursor.getInt(mHasAttachmentsIndex) != 0;
- }
-
- public boolean hasMessagesWithErrors() {
- return mCursor.getInt(mHasMessagesWithErrorsIndex) != 0;
- }
-
- public boolean getForceAllUnread() {
- return !mCursor.isNull(mForceAllUnreadIndex)
- && mCursor.getInt(mForceAllUnreadIndex) != 0;
- }
- }
-}
diff --git a/core/java/android/provider/Im.java b/core/java/android/provider/Im.java
deleted file mode 100644
index 025d5c2..0000000
--- a/core/java/android/provider/Im.java
+++ /dev/null
@@ -1,2352 +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.provider;
-
-import android.content.ContentQueryMap;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-
-import java.util.HashMap;
-
-/**
- * The GTalk provider stores all information about roster contacts, chat messages, presence, etc.
- *
- * @hide
- */
-public class Im {
- /**
- * no public constructor since this is a utility class
- */
- private Im() {}
-
- /**
- * The Columns for IM providers
- */
- public interface ProviderColumns {
- /**
- * The name of the IM provider
- * <P>Type: TEXT</P>
- */
- String NAME = "name";
-
- /**
- * The full name of the provider
- * <P>Type: TEXT</P>
- */
- String FULLNAME = "fullname";
-
- /**
- * The category for the provider, used to form intent.
- * <P>Type: TEXT</P>
- */
- String CATEGORY = "category";
-
- /**
- * The url users should visit to create a new account for this provider
- * <P>Type: TEXT</P>
- */
- String SIGNUP_URL = "signup_url";
- }
-
- /**
- * Known names corresponding to the {@link ProviderColumns#NAME} column
- */
- public interface ProviderNames {
- //
- //NOTE: update Contacts.java with new providers when they're added.
- //
- String YAHOO = "Yahoo";
- String GTALK = "GTalk";
- String MSN = "MSN";
- String ICQ = "ICQ";
- String AIM = "AIM";
- String XMPP = "XMPP";
- String JABBER = "JABBER";
- String SKYPE = "SKYPE";
- String QQ = "QQ";
- }
-
- /**
- * This table contains the IM providers
- */
- public static final class Provider implements BaseColumns, ProviderColumns {
- private Provider() {}
-
- public static final long getProviderIdForName(ContentResolver cr, String providerName) {
- String[] selectionArgs = new String[1];
- selectionArgs[0] = providerName;
-
- Cursor cursor = cr.query(CONTENT_URI,
- PROVIDER_PROJECTION,
- NAME+"=?",
- selectionArgs, null);
-
- long retVal = 0;
- try {
- if (cursor.moveToFirst()) {
- retVal = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
- }
- } finally {
- cursor.close();
- }
-
- return retVal;
- }
-
- public static final String getProviderNameForId(ContentResolver cr, long providerId) {
- Cursor cursor = cr.query(CONTENT_URI,
- PROVIDER_PROJECTION,
- _ID + "=" + providerId,
- null, null);
-
- String retVal = null;
- try {
- if (cursor.moveToFirst()) {
- retVal = cursor.getString(cursor.getColumnIndexOrThrow(NAME));
- }
- } finally {
- cursor.close();
- }
-
- return retVal;
- }
-
- private static final String[] PROVIDER_PROJECTION = new String[] {
- _ID,
- NAME
- };
-
- public static final String ACTIVE_ACCOUNT_ID = "account_id";
- public static final String ACTIVE_ACCOUNT_USERNAME = "account_username";
- public static final String ACTIVE_ACCOUNT_PW = "account_pw";
- public static final String ACTIVE_ACCOUNT_LOCKED = "account_locked";
- public static final String ACTIVE_ACCOUNT_KEEP_SIGNED_IN = "account_keepSignedIn";
- public static final String ACCOUNT_PRESENCE_STATUS = "account_presenceStatus";
- public static final String ACCOUNT_CONNECTION_STATUS = "account_connStatus";
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/providers");
-
- public static final Uri CONTENT_URI_WITH_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/providers/account");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-providers";
-
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-providers";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "name ASC";
- }
-
- /**
- * The columns for IM accounts. There can be more than one account for each IM provider.
- */
- public interface AccountColumns {
- /**
- * The name of the account
- * <P>Type: TEXT</P>
- */
- String NAME = "name";
-
- /**
- * The IM provider for this account
- * <P>Type: INTEGER</P>
- */
- String PROVIDER = "provider";
-
- /**
- * The username for this account
- * <P>Type: TEXT</P>
- */
- String USERNAME = "username";
-
- /**
- * The password for this account
- * <P>Type: TEXT</P>
- */
- String PASSWORD = "pw";
-
- /**
- * A boolean value indicates if the account is active.
- * <P>Type: INTEGER</P>
- */
- String ACTIVE = "active";
-
- /**
- * A boolean value indicates if the account is locked (not editable)
- * <P>Type: INTEGER</P>
- */
- String LOCKED = "locked";
-
- /**
- * A boolean value to indicate whether this account is kept signed in.
- * <P>Type: INTEGER</P>
- */
- String KEEP_SIGNED_IN = "keep_signed_in";
-
- /**
- * A boolean value indiciating the last login state for this account
- * <P>Type: INTEGER</P>
- */
- String LAST_LOGIN_STATE = "last_login_state";
- }
-
- /**
- * This table contains the IM accounts.
- */
- public static final class Account implements BaseColumns, AccountColumns {
- private Account() {}
-
- public static final long getProviderIdForAccount(ContentResolver cr, long accountId) {
- Cursor cursor = cr.query(CONTENT_URI,
- PROVIDER_PROJECTION,
- _ID + "=" + accountId,
- null /* selection args */,
- null /* sort order */);
-
- long providerId = 0;
-
- try {
- if (cursor.moveToFirst()) {
- providerId = cursor.getLong(PROVIDER_COLUMN);
- }
- } finally {
- cursor.close();
- }
-
- return providerId;
- }
-
- private static final String[] PROVIDER_PROJECTION = new String[] { PROVIDER };
- private static final int PROVIDER_COLUMN = 0;
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/accounts");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * account.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-accounts";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * account.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-accounts";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "name ASC";
-
- }
-
- /**
- * Connection status
- */
- public interface ConnectionStatus {
- /**
- * The connection is offline, not logged in.
- */
- int OFFLINE = 0;
-
- /**
- * The connection is attempting to connect.
- */
- int CONNECTING = 1;
-
- /**
- * The connection is suspended due to network not available.
- */
- int SUSPENDED = 2;
-
- /**
- * The connection is logged in and online.
- */
- int ONLINE = 3;
- }
-
- public interface AccountStatusColumns {
- /**
- * account id
- * <P>Type: INTEGER</P>
- */
- String ACCOUNT = "account";
-
- /**
- * User's presence status, see definitions in {#link CommonPresenceColumn}
- * <P>Type: INTEGER</P>
- */
- String PRESENCE_STATUS = "presenceStatus";
-
- /**
- * The connection status of this account, see {#link ConnectionStatus}
- * <P>Type: INTEGER</P>
- */
- String CONNECTION_STATUS = "connStatus";
- }
-
- public static final class AccountStatus implements BaseColumns, AccountStatusColumns {
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/accountStatus");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of account status.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-account-status";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single account status.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-account-status";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "name ASC";
- }
-
- /**
- * Columns from the Contacts table.
- */
- public interface ContactsColumns {
- /**
- * The username
- * <P>Type: TEXT</P>
- */
- String USERNAME = "username";
-
- /**
- * The nickname or display name
- * <P>Type: TEXT</P>
- */
- String NICKNAME = "nickname";
-
- /**
- * The IM provider for this contact
- * <P>Type: INTEGER</P>
- */
- String PROVIDER = "provider";
-
- /**
- * The account (within a IM provider) for this contact
- * <P>Type: INTEGER</P>
- */
- String ACCOUNT = "account";
-
- /**
- * The contactList this contact belongs to
- * <P>Type: INTEGER</P>
- */
- String CONTACTLIST = "contactList";
-
- /**
- * Contact type
- * <P>Type: INTEGER</P>
- */
- String TYPE = "type";
-
- /**
- * normal IM contact
- */
- int TYPE_NORMAL = 0;
- /**
- * temporary contact, someone not in the list of contacts that we
- * subscribe presence for. Usually created because of the user is
- * having a chat session with this contact.
- */
- int TYPE_TEMPORARY = 1;
- /**
- * temporary contact created for group chat.
- */
- int TYPE_GROUP = 2;
- /**
- * blocked contact.
- */
- int TYPE_BLOCKED = 3;
- /**
- * the contact is hidden. The client should always display this contact to the user.
- */
- int TYPE_HIDDEN = 4;
- /**
- * the contact is pinned. The client should always display this contact to the user.
- */
- int TYPE_PINNED = 5;
-
- /**
- * Contact subscription status
- * <P>Type: INTEGER</P>
- */
- String SUBSCRIPTION_STATUS = "subscriptionStatus";
-
- /**
- * no pending subscription
- */
- int SUBSCRIPTION_STATUS_NONE = 0;
- /**
- * requested to subscribe
- */
- int SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING = 1;
- /**
- * requested to unsubscribe
- */
- int SUBSCRIPTION_STATUS_UNSUBSCRIBE_PENDING = 2;
-
- /**
- * Contact subscription type
- * <P>Type: INTEGER </P>
- */
- String SUBSCRIPTION_TYPE = "subscriptionType";
-
- /**
- * The user and contact have no interest in each other's presence.
- */
- int SUBSCRIPTION_TYPE_NONE = 0;
- /**
- * The user wishes to stop receiving presence updates from the contact.
- */
- int SUBSCRIPTION_TYPE_REMOVE = 1;
- /**
- * The user is interested in receiving presence updates from the contact.
- */
- int SUBSCRIPTION_TYPE_TO = 2;
- /**
- * The contact is interested in receiving presence updates from the user.
- */
- int SUBSCRIPTION_TYPE_FROM = 3;
- /**
- * The user and contact have a mutual interest in each other's presence.
- */
- int SUBSCRIPTION_TYPE_BOTH = 4;
- /**
- * This is a special type reserved for pending subscription requests
- */
- int SUBSCRIPTION_TYPE_INVITATIONS = 5;
-
- /**
- * Quick Contact: derived from Google Contact Extension's "message_count" attribute.
- * <P>Type: INTEGER</P>
- */
- String QUICK_CONTACT = "qc";
-
- /**
- * Google Contact Extension attribute
- *
- * Rejected: a boolean value indicating whether a subscription request from
- * this client was ever rejected by the user. "true" indicates that it has.
- * This is provided so that a client can block repeated subscription requests.
- * <P>Type: INTEGER</P>
- */
- String REJECTED = "rejected";
-
- /**
- * Off The Record status: 0 for disabled, 1 for enabled
- * <P>Type: INTEGER </P>
- */
- String OTR = "otr";
- }
-
- /**
- * This defines the different type of values of {@link ContactsColumns#OTR}
- */
- public interface OffTheRecordType {
- /*
- * Off the record not turned on
- */
- int DISABLED = 0;
- /**
- * Off the record turned on, but we don't know who turned it on
- */
- int ENABLED = 1;
- /**
- * Off the record turned on by the user
- */
- int ENABLED_BY_USER = 2;
- /**
- * Off the record turned on by the buddy
- */
- int ENABLED_BY_BUDDY = 3;
- };
-
- /**
- * This table contains contacts.
- */
- public static final class Contacts implements BaseColumns,
- ContactsColumns, PresenceColumns, ChatsColumns {
- /**
- * no public constructor since this is a utility class
- */
- private Contacts() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/contacts");
-
- /**
- * The content:// style URL for contacts joined with presence
- */
- public static final Uri CONTENT_URI_WITH_PRESENCE =
- Uri.parse("content://com.google.android.providers.talk/contactsWithPresence");
-
- /**
- * The content:// style URL for barebone contacts, not joined with any other table
- */
- public static final Uri CONTENT_URI_CONTACTS_BAREBONE =
- Uri.parse("content://com.google.android.providers.talk/contactsBarebone");
-
- /**
- * The content:// style URL for contacts who have an open chat session
- */
- public static final Uri CONTENT_URI_CHAT_CONTACTS =
- Uri.parse("content://com.google.android.providers.talk/contacts_chatting");
-
- /**
- * The content:// style URL for contacts who have been blocked
- */
- public static final Uri CONTENT_URI_BLOCKED_CONTACTS =
- Uri.parse("content://com.google.android.providers.talk/contacts/blocked");
-
- /**
- * The content:// style URL for contacts by provider and account
- */
- public static final Uri CONTENT_URI_CONTACTS_BY =
- Uri.parse("content://com.google.android.providers.talk/contacts");
-
- /**
- * The content:// style URL for contacts by provider and account,
- * and who have an open chat session
- */
- public static final Uri CONTENT_URI_CHAT_CONTACTS_BY =
- Uri.parse("content://com.google.android.providers.talk/contacts/chatting");
-
- /**
- * The content:// style URL for contacts by provider and account,
- * and who are online
- */
- public static final Uri CONTENT_URI_ONLINE_CONTACTS_BY =
- Uri.parse("content://com.google.android.providers.talk/contacts/online");
-
- /**
- * The content:// style URL for contacts by provider and account,
- * and who are offline
- */
- public static final Uri CONTENT_URI_OFFLINE_CONTACTS_BY =
- Uri.parse("content://com.google.android.providers.talk/contacts/offline");
-
- /**
- * The content:// style URL for operations on bulk contacts
- */
- public static final Uri BULK_CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/bulk_contacts");
-
- /**
- * The content:// style URL for the count of online contacts in each
- * contact list by provider and account.
- */
- public static final Uri CONTENT_URI_ONLINE_COUNT =
- Uri.parse("content://com.google.android.providers.talk/contacts/onlineCount");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-contacts";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/gtalk-contacts";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER =
- "subscriptionType DESC, last_message_date DESC," +
- " mode DESC, nickname COLLATE UNICODE ASC";
-
- public static final String CHATS_CONTACT = "chats_contact";
-
- public static final String AVATAR_HASH = "avatars_hash";
-
- public static final String AVATAR_DATA = "avatars_data";
- }
-
- /**
- * Columns from the ContactList table.
- */
- public interface ContactListColumns {
- String NAME = "name";
- String PROVIDER = "provider";
- String ACCOUNT = "account";
- }
-
- /**
- * This table contains the contact lists.
- */
- public static final class ContactList implements BaseColumns,
- ContactListColumns {
- private ContactList() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/contactLists");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-contactLists";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-contactLists";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "name COLLATE UNICODE ASC";
-
- public static final String PROVIDER_NAME = "provider_name";
-
- public static final String ACCOUNT_NAME = "account_name";
- }
-
- /**
- * Columns from the BlockedList table.
- */
- public interface BlockedListColumns {
- /**
- * The username of the blocked contact.
- * <P>Type: TEXT</P>
- */
- String USERNAME = "username";
-
- /**
- * The nickname of the blocked contact.
- * <P>Type: TEXT</P>
- */
- String NICKNAME = "nickname";
-
- /**
- * The provider id of the blocked contact.
- * <P>Type: INT</P>
- */
- String PROVIDER = "provider";
-
- /**
- * The account id of the blocked contact.
- * <P>Type: INT</P>
- */
- String ACCOUNT = "account";
- }
-
- /**
- * This table contains blocked lists
- */
- public static final class BlockedList implements BaseColumns, BlockedListColumns {
- private BlockedList() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/blockedList");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-blockedList";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-blockedList";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "nickname ASC";
-
- public static final String PROVIDER_NAME = "provider_name";
-
- public static final String ACCOUNT_NAME = "account_name";
-
- public static final String AVATAR_DATA = "avatars_data";
- }
-
- /**
- * Columns from the contactsEtag table
- */
- public interface ContactsEtagColumns {
- /**
- * The roster etag, computed by the server, stored on the client. There is one etag
- * per account roster.
- * <P>Type: TEXT</P>
- */
- String ETAG = "etag";
-
- /**
- * The OTR etag, computed by the server, stored on the client. There is one OTR etag
- * per account roster.
- * <P>Type: TEXT</P>
- */
- String OTR_ETAG = "otr_etag";
-
- /**
- * The account id for the etag.
- * <P> Type: INTEGER </P>
- */
- String ACCOUNT = "account";
- }
-
- public static final class ContactsEtag implements BaseColumns, ContactsEtagColumns {
- private ContactsEtag() {}
-
- public static final Cursor query(ContentResolver cr,
- String[] projection) {
- return cr.query(CONTENT_URI, projection, null, null, null);
- }
-
- public static final Cursor query(ContentResolver cr,
- String[] projection, String where, String orderBy) {
- return cr.query(CONTENT_URI, projection, where,
- null, orderBy == null ? null : orderBy);
- }
-
- public static final String getRosterEtag(ContentResolver resolver, long accountId) {
- String retVal = null;
-
- Cursor c = resolver.query(CONTENT_URI,
- CONTACT_ETAG_PROJECTION,
- ACCOUNT + "=" + accountId,
- null /* selection args */,
- null /* sort order */);
-
- try {
- if (c.moveToFirst()) {
- retVal = c.getString(COLUMN_ETAG);
- }
- } finally {
- c.close();
- }
-
- return retVal;
- }
-
- public static final String getOtrEtag(ContentResolver resolver, long accountId) {
- String retVal = null;
-
- Cursor c = resolver.query(CONTENT_URI,
- CONTACT_OTR_ETAG_PROJECTION,
- ACCOUNT + "=" + accountId,
- null /* selection args */,
- null /* sort order */);
-
- try {
- if (c.moveToFirst()) {
- retVal = c.getString(COLUMN_OTR_ETAG);
- }
- } finally {
- c.close();
- }
-
- return retVal;
- }
-
- private static final String[] CONTACT_ETAG_PROJECTION = new String[] {
- Im.ContactsEtag.ETAG // 0
- };
-
- private static int COLUMN_ETAG = 0;
-
- private static final String[] CONTACT_OTR_ETAG_PROJECTION = new String[] {
- Im.ContactsEtag.OTR_ETAG // 0
- };
-
- private static int COLUMN_OTR_ETAG = 0;
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/contactsEtag");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-contactsEtag";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-contactsEtag";
- }
-
- /**
- * Message type definition
- */
- public interface MessageType {
- /* sent message */
- int OUTGOING = 0;
- /* received message */
- int INCOMING = 1;
- /* presence became available */
- int PRESENCE_AVAILABLE = 2;
- /* presence became away */
- int PRESENCE_AWAY = 3;
- /* presence became DND (busy) */
- int PRESENCE_DND = 4;
- /* presence became unavailable */
- int PRESENCE_UNAVAILABLE = 5;
- /* the message is converted to a group chat */
- int CONVERT_TO_GROUPCHAT = 6;
- /* generic status */
- int STATUS = 7;
- /* the message cannot be sent now, but will be sent later */
- int POSTPONED = 8;
- /* off The Record status is turned off */
- int OTR_IS_TURNED_OFF = 9;
- /* off the record status is turned on */
- int OTR_IS_TURNED_ON = 10;
- /* off the record status turned on by user */
- int OTR_TURNED_ON_BY_USER = 11;
- /* off the record status turned on by buddy */
- int OTR_TURNED_ON_BY_BUDDY = 12;
- }
-
- /**
- * The common columns for messages table
- */
- public interface MessageColumns {
- /**
- * The thread_id column stores the contact id of the contact the message belongs to.
- * For groupchat messages, the thread_id stores the group id, which is the contact id
- * of the temporary group contact created for the groupchat. So there should be no
- * collision between groupchat message thread id and regular message thread id.
- */
- String THREAD_ID = "thread_id";
-
- /**
- * The nickname. This is used for groupchat messages to indicate the participant's
- * nickname. For non groupchat messages, this field should be left empty.
- */
- String NICKNAME = "nickname";
-
- /**
- * The body
- * <P>Type: TEXT</P>
- */
- String BODY = "body";
-
- /**
- * The date this message is sent or received. This represents the display date for
- * the message.
- * <P>Type: INTEGER</P>
- */
- String DATE = "date";
-
- /**
- * The real date for this message. While 'date' can be modified by the client
- * to account for server time skew, the real_date is the original timestamp set
- * by the server for incoming messages.
- * <P>Type: INTEGER</P>
- */
- String REAL_DATE = "real_date";
-
- /**
- * Message Type, see {@link MessageType}
- * <P>Type: INTEGER</P>
- */
- String TYPE = "type";
-
- /**
- * Error Code: 0 means no error.
- * <P>Type: INTEGER </P>
- */
- String ERROR_CODE = "err_code";
-
- /**
- * Error Message
- * <P>Type: TEXT</P>
- */
- String ERROR_MESSAGE = "err_msg";
-
- /**
- * Packet ID, auto assigned by the GTalkService for outgoing messages or the
- * GTalk server for incoming messages. The packet id field is optional for messages,
- * so it could be null.
- * <P>Type: STRING</P>
- */
- String PACKET_ID = "packet_id";
-
- /**
- * Is groupchat message or not
- * <P>Type: INTEGER</P>
- */
- String IS_GROUP_CHAT = "is_muc";
-
- /**
- * A hint that the UI should show the sent time of this message
- * <P>Type: INTEGER</P>
- */
- String DISPLAY_SENT_TIME = "show_ts";
- }
-
- /**
- * This table contains messages.
- */
- public static final class Messages implements BaseColumns, MessageColumns {
- /**
- * no public constructor since this is a utility class
- */
- private Messages() {}
-
- /**
- * Gets the Uri to query messages by thread id.
- *
- * @param threadId the thread id of the message.
- * @return the Uri
- */
- public static final Uri getContentUriByThreadId(long threadId) {
- Uri.Builder builder = CONTENT_URI_MESSAGES_BY_THREAD_ID.buildUpon();
- ContentUris.appendId(builder, threadId);
- return builder.build();
- }
-
- /**
- * @deprecated
- *
- * Gets the Uri to query messages by account and contact.
- *
- * @param accountId the account id of the contact.
- * @param username the user name of the contact.
- * @return the Uri
- */
- public static final Uri getContentUriByContact(long accountId, String username) {
- Uri.Builder builder = CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT.buildUpon();
- ContentUris.appendId(builder, accountId);
- builder.appendPath(username);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query messages by provider.
- *
- * @param providerId the service provider id.
- * @return the Uri
- */
- public static final Uri getContentUriByProvider(long providerId) {
- Uri.Builder builder = CONTENT_URI_MESSAGES_BY_PROVIDER.buildUpon();
- ContentUris.appendId(builder, providerId);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query off the record messages by account.
- *
- * @param accountId the account id.
- * @return the Uri
- */
- public static final Uri getContentUriByAccount(long accountId) {
- Uri.Builder builder = CONTENT_URI_BY_ACCOUNT.buildUpon();
- ContentUris.appendId(builder, accountId);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query off the record messages by thread id.
- *
- * @param threadId the thread id of the message.
- * @return the Uri
- */
- public static final Uri getOtrMessagesContentUriByThreadId(long threadId) {
- Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_THREAD_ID.buildUpon();
- ContentUris.appendId(builder, threadId);
- return builder.build();
- }
-
- /**
- * @deprecated
- *
- * Gets the Uri to query off the record messages by account and contact.
- *
- * @param accountId the account id of the contact.
- * @param username the user name of the contact.
- * @return the Uri
- */
- public static final Uri getOtrMessagesContentUriByContact(long accountId, String username) {
- Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT_AND_CONTACT.buildUpon();
- ContentUris.appendId(builder, accountId);
- builder.appendPath(username);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query off the record messages by provider.
- *
- * @param providerId the service provider id.
- * @return the Uri
- */
- public static final Uri getOtrMessagesContentUriByProvider(long providerId) {
- Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_PROVIDER.buildUpon();
- ContentUris.appendId(builder, providerId);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query off the record messages by account.
- *
- * @param accountId the account id.
- * @return the Uri
- */
- public static final Uri getOtrMessagesContentUriByAccount(long accountId) {
- Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT.buildUpon();
- ContentUris.appendId(builder, accountId);
- return builder.build();
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/messages");
-
- /**
- * The content:// style URL for messages by thread id
- */
- public static final Uri CONTENT_URI_MESSAGES_BY_THREAD_ID =
- Uri.parse("content://com.google.android.providers.talk/messagesByThreadId");
-
- /**
- * The content:// style URL for messages by account and contact
- */
- public static final Uri CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT =
- Uri.parse("content://com.google.android.providers.talk/messagesByAcctAndContact");
-
- /**
- * The content:// style URL for messages by provider
- */
- public static final Uri CONTENT_URI_MESSAGES_BY_PROVIDER =
- Uri.parse("content://com.google.android.providers.talk/messagesByProvider");
-
- /**
- * The content:// style URL for messages by account
- */
- public static final Uri CONTENT_URI_BY_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/messagesByAccount");
-
- /**
- * The content:// style url for off the record messages
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/otrMessages");
-
- /**
- * The content:// style url for off the record messages by thread id
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI_BY_THREAD_ID =
- Uri.parse("content://com.google.android.providers.talk/otrMessagesByThreadId");
-
- /**
- * The content:// style url for off the record messages by account and contact
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT_AND_CONTACT =
- Uri.parse("content://com.google.android.providers.talk/otrMessagesByAcctAndContact");
-
- /**
- * The content:// style URL for off the record messages by provider
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI_BY_PROVIDER =
- Uri.parse("content://com.google.android.providers.talk/otrMessagesByProvider");
-
- /**
- * The content:// style URL for off the record messages by account
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/otrMessagesByAccount");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-messages";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-messages";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "date ASC";
-
- /**
- * The "contact" column. This is not a real column in the messages table, but a
- * temoprary column created when querying for messages (joined with the contacts table)
- */
- public static final String CONTACT = "contact";
- }
-
- /**
- * Columns for the GroupMember table.
- */
- public interface GroupMemberColumns {
- /**
- * The id of the group this member belongs to.
- * <p>Type: INTEGER</p>
- */
- String GROUP = "groupId";
-
- /**
- * The full name of this member.
- * <p>Type: TEXT</p>
- */
- String USERNAME = "username";
-
- /**
- * The nick name of this member.
- * <p>Type: TEXT</p>
- */
- String NICKNAME = "nickname";
- }
-
- public final static class GroupMembers implements GroupMemberColumns {
- private GroupMembers(){}
-
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/groupMembers");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * group members.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-groupMembers";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * group member.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-groupMembers";
- }
-
- /**
- * Columns from the Invitation table.
- */
- public interface InvitationColumns {
- /**
- * The provider id.
- * <p>Type: INTEGER</p>
- */
- String PROVIDER = "providerId";
-
- /**
- * The account id.
- * <p>Type: INTEGER</p>
- */
- String ACCOUNT = "accountId";
-
- /**
- * The invitation id.
- * <p>Type: TEXT</p>
- */
- String INVITE_ID = "inviteId";
-
- /**
- * The name of the sender of the invitation.
- * <p>Type: TEXT</p>
- */
- String SENDER = "sender";
-
- /**
- * The name of the group which the sender invite you to join.
- * <p>Type: TEXT</p>
- */
- String GROUP_NAME = "groupName";
-
- /**
- * A note
- * <p>Type: TEXT</p>
- */
- String NOTE = "note";
-
- /**
- * The current status of the invitation.
- * <p>Type: TEXT</p>
- */
- String STATUS = "status";
-
- int STATUS_PENDING = 0;
- int STATUS_ACCEPTED = 1;
- int STATUS_REJECTED = 2;
- }
-
- /**
- * This table contains the invitations received from others.
- */
- public final static class Invitation implements InvitationColumns,
- BaseColumns {
- private Invitation() {
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/invitations");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * invitations.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-invitations";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * invitation.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-invitations";
- }
-
- /**
- * Columns from the Avatars table
- */
- public interface AvatarsColumns {
- /**
- * The contact this avatar belongs to
- * <P>Type: TEXT</P>
- */
- String CONTACT = "contact";
-
- String PROVIDER = "provider_id";
-
- String ACCOUNT = "account_id";
-
- /**
- * The hash of the image data
- * <P>Type: TEXT</P>
- */
- String HASH = "hash";
-
- /**
- * raw image data
- * <P>Type: BLOB</P>
- */
- String DATA = "data";
- }
-
- /**
- * This table contains avatars.
- */
- public static final class Avatars implements BaseColumns, AvatarsColumns {
- /**
- * no public constructor since this is a utility class
- */
- private Avatars() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/avatars");
-
- /**
- * The content:// style URL for avatars by provider, account and contact
- */
- public static final Uri CONTENT_URI_AVATARS_BY =
- Uri.parse("content://com.google.android.providers.talk/avatarsBy");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing the avatars
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-avatars";
-
- /**
- * The MIME type of a {@link #CONTENT_URI}
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-avatars";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "contact ASC";
-
- }
-
- /**
- * Common presence columns shared between the IM and contacts presence tables
- */
- public interface CommonPresenceColumns {
- /**
- * The priority, an integer, used by XMPP presence
- * <P>Type: INTEGER</P>
- */
- String PRIORITY = "priority";
-
- /**
- * The server defined status.
- * <P>Type: INTEGER (one of the values below)</P>
- */
- String PRESENCE_STATUS = "mode";
-
- /**
- * Presence Status definition
- */
- int OFFLINE = 0;
- int INVISIBLE = 1;
- int AWAY = 2;
- int IDLE = 3;
- int DO_NOT_DISTURB = 4;
- int AVAILABLE = 5;
-
- /**
- * The user defined status line.
- * <P>Type: TEXT</P>
- */
- String PRESENCE_CUSTOM_STATUS = "status";
- }
-
- /**
- * Columns from the Presence table.
- */
- public interface PresenceColumns extends CommonPresenceColumns {
- /**
- * The contact id
- * <P>Type: INTEGER</P>
- */
- String CONTACT_ID = "contact_id";
-
- /**
- * The contact's JID resource, only relevant for XMPP contact
- * <P>Type: TEXT</P>
- */
- String JID_RESOURCE = "jid_resource";
-
- /**
- * The contact's client type
- */
- String CLIENT_TYPE = "client_type";
-
- /**
- * client type definitions
- */
- int CLIENT_TYPE_DEFAULT = 0;
- int CLIENT_TYPE_MOBILE = 1;
- int CLIENT_TYPE_ANDROID = 2;
- }
-
- /**
- * Contains presence infomation for contacts.
- */
- public static final class Presence implements BaseColumns, PresenceColumns {
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/presence");
-
- /**
- * The content URL for Talk presences for an account
- */
- public static final Uri CONTENT_URI_BY_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/presence/account");
-
- /**
- * The content:// style URL for operations on bulk contacts
- */
- public static final Uri BULK_CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/bulk_presence");
-
- /**
- * The content:// style URL for seeding presences for a given account id.
- */
- public static final Uri SEED_PRESENCE_BY_ACCOUNT_CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/seed_presence/account");
-
- /**
- * The MIME type of a {@link #CONTENT_URI} providing a directory of presence
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-presence";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "mode DESC";
- }
-
- /**
- * Columns from the Chats table.
- */
- public interface ChatsColumns {
- /**
- * The contact ID this chat belongs to. The value is a long.
- * <P>Type: INT</P>
- */
- String CONTACT_ID = "contact_id";
-
- /**
- * The GTalk JID resource. The value is a string.
- * <P>Type: TEXT</P>
- */
- String JID_RESOURCE = "jid_resource";
-
- /**
- * Whether this is a groupchat or not.
- * <P>Type: INT</P>
- */
- String GROUP_CHAT = "groupchat";
-
- /**
- * The last unread message. This both indicates that there is an
- * unread message, and what the message is.
- * <P>Type: TEXT</P>
- */
- String LAST_UNREAD_MESSAGE = "last_unread_message";
-
- /**
- * The last message timestamp
- * <P>Type: INT</P>
- */
- String LAST_MESSAGE_DATE = "last_message_date";
-
- /**
- * A message that is being composed. This indicates that there was a
- * message being composed when the chat screen was shutdown, and what the
- * message is.
- * <P>Type: TEXT</P>
- */
- String UNSENT_COMPOSED_MESSAGE = "unsent_composed_message";
-
- /**
- * A value from 0-9 indicating which quick-switch chat screen slot this
- * chat is occupying. If none (for instance, this is the 12th active chat)
- * then the value is -1.
- * <P>Type: INT</P>
- */
- String SHORTCUT = "shortcut";
- }
-
- /**
- * Contains ongoing chat sessions.
- */
- public static final class Chats implements BaseColumns, ChatsColumns {
- /**
- * no public constructor since this is a utility class
- */
- private Chats() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/chats");
-
- /**
- * The content URL for all chats that belong to the account
- */
- public static final Uri CONTENT_URI_BY_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/chats/account");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of chats.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-chats";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single chat.
- */
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/gtalk-chats";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "last_message_date ASC";
- }
-
- /**
- * Columns from session cookies table. Used for IMPS.
- */
- public static interface SessionCookiesColumns {
- String NAME = "name";
- String VALUE = "value";
- String PROVIDER = "provider";
- String ACCOUNT = "account";
- }
-
- /**
- * Contains IMPS session cookies.
- */
- public static class SessionCookies implements SessionCookiesColumns, BaseColumns {
- private SessionCookies() {
- }
-
- /**
- * The content:// style URI for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/sessionCookies");
-
- /**
- * The content:// style URL for session cookies by provider and account
- */
- public static final Uri CONTENT_URI_SESSION_COOKIES_BY =
- Uri.parse("content://com.google.android.providers.talk/sessionCookiesBy");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE = "vnd.android-dir/gtalk-sessionCookies";
- }
-
- /**
- * Columns from ProviderSettings table
- */
- public static interface ProviderSettingsColumns {
- /**
- * The id in database of the related provider
- *
- * <P>Type: INT</P>
- */
- String PROVIDER = "provider";
-
- /**
- * The name of the setting
- * <P>Type: TEXT</P>
- */
- String NAME = "name";
-
- /**
- * The value of the setting
- * <P>Type: TEXT</P>
- */
- String VALUE = "value";
- }
-
- public static class ProviderSettings implements ProviderSettingsColumns {
- private ProviderSettings() {
- }
-
- /**
- * The content:// style URI for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/providerSettings");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing provider settings
- */
- public static final String CONTENT_TYPE = "vnd.android-dir/gtalk-providerSettings";
-
- /**
- * A boolean value to indicate whether this provider should show the offline contacts
- */
- public static final String SHOW_OFFLINE_CONTACTS = "show_offline_contacts";
-
- /** controls whether or not the GTalk service automatically connect to server. */
- public static final String SETTING_AUTOMATICALLY_CONNECT_GTALK = "gtalk_auto_connect";
-
- /** controls whether or not the GTalk service will be automatically started after boot */
- public static final String SETTING_AUTOMATICALLY_START_SERVICE = "auto_start_service";
-
- /** controls whether or not the offline contacts will be hided */
- public static final String SETTING_HIDE_OFFLINE_CONTACTS = "hide_offline_contacts";
-
- /** controls whether or not enable the GTalk notification */
- public static final String SETTING_ENABLE_NOTIFICATION = "enable_notification";
-
- /** specifies whether or not to vibrate */
- public static final String SETTING_VIBRATE = "vibrate";
-
- /** specifies the Uri string of the ringtone */
- public static final String SETTING_RINGTONE = "ringtone";
-
- /** specifies the Uri of the default ringtone */
- public static final String SETTING_RINGTONE_DEFAULT =
- "content://settings/system/notification_sound";
-
- /** specifies whether or not to show mobile indicator to friends */
- public static final String SETTING_SHOW_MOBILE_INDICATOR = "mobile_indicator";
-
- /** specifies whether or not to show as away when device is idle */
- public static final String SETTING_SHOW_AWAY_ON_IDLE = "show_away_on_idle";
-
- /** specifies whether or not to upload heartbeat stat upon login */
- public static final String SETTING_UPLOAD_HEARTBEAT_STAT = "upload_heartbeat_stat";
-
- /** specifies the last heartbeat interval received from the server */
- public static final String SETTING_HEARTBEAT_INTERVAL = "heartbeat_interval";
-
- /** specifiy the JID resource used for Google Talk connection */
- public static final String SETTING_JID_RESOURCE = "jid_resource";
-
- /**
- * Used for reliable message queue (RMQ). This is for storing the last rmq id received
- * from the GTalk server
- */
- public static final String LAST_RMQ_RECEIVED = "last_rmq_rec";
-
- /**
- * Query the settings of the provider specified by id
- *
- * @param cr
- * the relative content resolver
- * @param providerId
- * the specified id of provider
- * @return a HashMap which contains all the settings for the specified
- * provider
- */
- public static HashMap<String, String> queryProviderSettings(ContentResolver cr,
- long providerId) {
- HashMap<String, String> settings = new HashMap<String, String>();
-
- String[] projection = { NAME, VALUE };
- Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), projection, null, null, null);
- if (c == null) {
- return null;
- }
-
- while(c.moveToNext()) {
- settings.put(c.getString(0), c.getString(1));
- }
-
- c.close();
-
- return settings;
- }
-
- /**
- * Get the string value of setting which is specified by provider id and the setting name.
- *
- * @param cr The ContentResolver to use to access the settings table.
- * @param providerId The id of the provider.
- * @param settingName The name of the setting.
- * @return The value of the setting if the setting exist, otherwise return null.
- */
- public static String getStringValue(ContentResolver cr, long providerId, String settingName) {
- String ret = null;
- Cursor c = getSettingValue(cr, providerId, settingName);
- if (c != null) {
- ret = c.getString(0);
- c.close();
- }
-
- return ret;
- }
-
- /**
- * Get the boolean value of setting which is specified by provider id and the setting name.
- *
- * @param cr The ContentResolver to use to access the settings table.
- * @param providerId The id of the provider.
- * @param settingName The name of the setting.
- * @return The value of the setting if the setting exist, otherwise return false.
- */
- public static boolean getBooleanValue(ContentResolver cr, long providerId, String settingName) {
- boolean ret = false;
- Cursor c = getSettingValue(cr, providerId, settingName);
- if (c != null) {
- ret = c.getInt(0) != 0;
- c.close();
- }
- return ret;
- }
-
- private static Cursor getSettingValue(ContentResolver cr, long providerId, String settingName) {
- Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), new String[]{VALUE}, NAME + "=?",
- new String[]{settingName}, null);
- if (c != null) {
- if (!c.moveToFirst()) {
- c.close();
- return null;
- }
- }
- return c;
- }
-
- /**
- * Save a long value of setting in the table providerSetting.
- *
- * @param cr The ContentProvider used to access the providerSetting table.
- * @param providerId The id of the provider.
- * @param name The name of the setting.
- * @param value The value of the setting.
- */
- public static void putLongValue(ContentResolver cr, long providerId, String name,
- long value) {
- ContentValues v = new ContentValues(3);
- v.put(PROVIDER, providerId);
- v.put(NAME, name);
- v.put(VALUE, value);
-
- cr.insert(CONTENT_URI, v);
- }
-
- /**
- * Save a boolean value of setting in the table providerSetting.
- *
- * @param cr The ContentProvider used to access the providerSetting table.
- * @param providerId The id of the provider.
- * @param name The name of the setting.
- * @param value The value of the setting.
- */
- public static void putBooleanValue(ContentResolver cr, long providerId, String name,
- boolean value) {
- ContentValues v = new ContentValues(3);
- v.put(PROVIDER, providerId);
- v.put(NAME, name);
- v.put(VALUE, Boolean.toString(value));
-
- cr.insert(CONTENT_URI, v);
- }
-
- /**
- * Save a string value of setting in the table providerSetting.
- *
- * @param cr The ContentProvider used to access the providerSetting table.
- * @param providerId The id of the provider.
- * @param name The name of the setting.
- * @param value The value of the setting.
- */
- public static void putStringValue(ContentResolver cr, long providerId, String name,
- String value) {
- ContentValues v = new ContentValues(3);
- v.put(PROVIDER, providerId);
- v.put(NAME, name);
- v.put(VALUE, value);
-
- cr.insert(CONTENT_URI, v);
- }
-
- /**
- * A convenience method to set whether or not the GTalk service should be started
- * automatically.
- *
- * @param contentResolver The ContentResolver to use to access the settings table
- * @param autoConnect Whether the GTalk service should be started automatically.
- */
- public static void setAutomaticallyConnectGTalk(ContentResolver contentResolver,
- long providerId, boolean autoConnect) {
- putBooleanValue(contentResolver, providerId, SETTING_AUTOMATICALLY_CONNECT_GTALK,
- autoConnect);
- }
-
- /**
- * A convenience method to set whether or not the offline contacts should be hided
- *
- * @param contentResolver The ContentResolver to use to access the setting table
- * @param hideOfflineContacts Whether the offline contacts should be hided
- */
- public static void setHideOfflineContacts(ContentResolver contentResolver,
- long providerId, boolean hideOfflineContacts) {
- putBooleanValue(contentResolver, providerId, SETTING_HIDE_OFFLINE_CONTACTS,
- hideOfflineContacts);
- }
-
- /**
- * A convenience method to set whether or not enable the GTalk notification.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param enable Whether enable the GTalk notification
- */
- public static void setEnableNotification(ContentResolver contentResolver, long providerId,
- boolean enable) {
- putBooleanValue(contentResolver, providerId, SETTING_ENABLE_NOTIFICATION, enable);
- }
-
- /**
- * A convenience method to set whether or not to vibrate.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param vibrate Whether or not to vibrate
- */
- public static void setVibrate(ContentResolver contentResolver, long providerId,
- boolean vibrate) {
- putBooleanValue(contentResolver, providerId, SETTING_VIBRATE, vibrate);
- }
-
- /**
- * A convenience method to set the Uri String of the ringtone.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param ringtoneUri The Uri String of the ringtone to be set.
- */
- public static void setRingtoneURI(ContentResolver contentResolver, long providerId,
- String ringtoneUri) {
- putStringValue(contentResolver, providerId, SETTING_RINGTONE, ringtoneUri);
- }
-
- /**
- * A convenience method to set whether or not to show mobile indicator.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param showMobileIndicator Whether or not to show mobile indicator.
- */
- public static void setShowMobileIndicator(ContentResolver contentResolver, long providerId,
- boolean showMobileIndicator) {
- putBooleanValue(contentResolver, providerId, SETTING_SHOW_MOBILE_INDICATOR,
- showMobileIndicator);
- }
-
- /**
- * A convenience method to set whether or not to show as away when device is idle.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param showAway Whether or not to show as away when device is idle.
- */
- public static void setShowAwayOnIdle(ContentResolver contentResolver,
- long providerId, boolean showAway) {
- putBooleanValue(contentResolver, providerId, SETTING_SHOW_AWAY_ON_IDLE, showAway);
- }
-
- /**
- * A convenience method to set whether or not to upload heartbeat stat.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param uploadStat Whether or not to upload heartbeat stat.
- */
- public static void setUploadHeartbeatStat(ContentResolver contentResolver,
- long providerId, boolean uploadStat) {
- putBooleanValue(contentResolver, providerId, SETTING_UPLOAD_HEARTBEAT_STAT, uploadStat);
- }
-
- /**
- * A convenience method to set the heartbeat interval last received from the server.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param interval The heartbeat interval last received from the server.
- */
- public static void setHeartbeatInterval(ContentResolver contentResolver,
- long providerId, long interval) {
- putLongValue(contentResolver, providerId, SETTING_HEARTBEAT_INTERVAL, interval);
- }
-
- /**
- * A convenience method to set the jid resource.
- */
- public static void setJidResource(ContentResolver contentResolver,
- long providerId, String jidResource) {
- putStringValue(contentResolver, providerId, SETTING_JID_RESOURCE, jidResource);
- }
-
- public static class QueryMap extends ContentQueryMap {
- private ContentResolver mContentResolver;
- private long mProviderId;
-
- public QueryMap(ContentResolver contentResolver, long providerId, boolean keepUpdated,
- Handler handlerForUpdateNotifications) {
- super(contentResolver.query(CONTENT_URI,
- new String[] {NAME,VALUE},
- PROVIDER + "=" + providerId,
- null, // no selection args
- null), // no sort order
- NAME, keepUpdated, handlerForUpdateNotifications);
- mContentResolver = contentResolver;
- mProviderId = providerId;
- }
-
- /**
- * Set if the GTalk service should automatically connect to server.
- *
- * @param autoConnect if the GTalk service should auto connect to server.
- */
- public void setAutomaticallyConnectToGTalkServer(boolean autoConnect) {
- ProviderSettings.setAutomaticallyConnectGTalk(mContentResolver, mProviderId,
- autoConnect);
- }
-
- /**
- * Check if the GTalk service should automatically connect to server.
- * @return if the GTalk service should automatically connect to server.
- */
- public boolean getAutomaticallyConnectToGTalkServer() {
- return getBoolean(SETTING_AUTOMATICALLY_CONNECT_GTALK,
- true /* default to automatically sign in */);
- }
-
- /**
- * Set whether or not the offline contacts should be hided.
- *
- * @param hideOfflineContacts Whether or not the offline contacts should be hided.
- */
- public void setHideOfflineContacts(boolean hideOfflineContacts) {
- ProviderSettings.setHideOfflineContacts(mContentResolver, mProviderId,
- hideOfflineContacts);
- }
-
- /**
- * Check if the offline contacts should be hided.
- *
- * @return Whether or not the offline contacts should be hided.
- */
- public boolean getHideOfflineContacts() {
- return getBoolean(SETTING_HIDE_OFFLINE_CONTACTS,
- false/* by default not hide the offline contacts*/);
- }
-
- /**
- * Set whether or not enable the GTalk notification.
- *
- * @param enable Whether or not enable the GTalk notification.
- */
- public void setEnableNotification(boolean enable) {
- ProviderSettings.setEnableNotification(mContentResolver, mProviderId, enable);
- }
-
- /**
- * Check if the GTalk notification is enabled.
- *
- * @return Whether or not enable the GTalk notification.
- */
- public boolean getEnableNotification() {
- return getBoolean(SETTING_ENABLE_NOTIFICATION,
- true/* by default enable the notification */);
- }
-
- /**
- * Set whether or not to vibrate on GTalk notification.
- *
- * @param vibrate Whether or not to vibrate.
- */
- public void setVibrate(boolean vibrate) {
- ProviderSettings.setVibrate(mContentResolver, mProviderId, vibrate);
- }
-
- /**
- * Gets whether or not to vibrate on GTalk notification.
- *
- * @return Whether or not to vibrate.
- */
- public boolean getVibrate() {
- return getBoolean(SETTING_VIBRATE, false /* by default disable vibrate */);
- }
-
- /**
- * Set the Uri for the ringtone.
- *
- * @param ringtoneUri The Uri of the ringtone to be set.
- */
- public void setRingtoneURI(String ringtoneUri) {
- ProviderSettings.setRingtoneURI(mContentResolver, mProviderId, ringtoneUri);
- }
-
- /**
- * Get the Uri String of the current ringtone.
- *
- * @return The Uri String of the current ringtone.
- */
- public String getRingtoneURI() {
- return getString(SETTING_RINGTONE, SETTING_RINGTONE_DEFAULT);
- }
-
- /**
- * Set whether or not to show mobile indicator to friends.
- *
- * @param showMobile whether or not to show mobile indicator.
- */
- public void setShowMobileIndicator(boolean showMobile) {
- ProviderSettings.setShowMobileIndicator(mContentResolver, mProviderId, showMobile);
- }
-
- /**
- * Gets whether or not to show mobile indicator.
- *
- * @return Whether or not to show mobile indicator.
- */
- public boolean getShowMobileIndicator() {
- return getBoolean(SETTING_SHOW_MOBILE_INDICATOR,
- true /* by default show mobile indicator */);
- }
-
- /**
- * Set whether or not to show as away when device is idle.
- *
- * @param showAway whether or not to show as away when device is idle.
- */
- public void setShowAwayOnIdle(boolean showAway) {
- ProviderSettings.setShowAwayOnIdle(mContentResolver, mProviderId, showAway);
- }
-
- /**
- * Get whether or not to show as away when device is idle.
- *
- * @return Whether or not to show as away when device is idle.
- */
- public boolean getShowAwayOnIdle() {
- return getBoolean(SETTING_SHOW_AWAY_ON_IDLE,
- true /* by default show as away on idle*/);
- }
-
- /**
- * Set whether or not to upload heartbeat stat.
- *
- * @param uploadStat whether or not to upload heartbeat stat.
- */
- public void setUploadHeartbeatStat(boolean uploadStat) {
- ProviderSettings.setUploadHeartbeatStat(mContentResolver, mProviderId, uploadStat);
- }
-
- /**
- * Get whether or not to upload heartbeat stat.
- *
- * @return Whether or not to upload heartbeat stat.
- */
- public boolean getUploadHeartbeatStat() {
- return getBoolean(SETTING_UPLOAD_HEARTBEAT_STAT,
- false /* by default do not upload */);
- }
-
- /**
- * Set the last received heartbeat interval from the server.
- *
- * @param interval the last received heartbeat interval from the server.
- */
- public void setHeartbeatInterval(long interval) {
- ProviderSettings.setHeartbeatInterval(mContentResolver, mProviderId, interval);
- }
-
- /**
- * Get the last received heartbeat interval from the server.
- *
- * @return the last received heartbeat interval from the server.
- */
- public long getHeartbeatInterval() {
- return getLong(SETTING_HEARTBEAT_INTERVAL, 0L /* an invalid default interval */);
- }
-
- /**
- * Set the JID resource.
- *
- * @param jidResource the jid resource to be stored.
- */
- public void setJidResource(String jidResource) {
- ProviderSettings.setJidResource(mContentResolver, mProviderId, jidResource);
- }
- /**
- * Get the JID resource used for the Google Talk connection
- *
- * @return the JID resource stored.
- */
- public String getJidResource() {
- return getString(SETTING_JID_RESOURCE, null);
- }
-
- /**
- * Convenience function for retrieving a single settings value
- * as a boolean.
- *
- * @param name The name of the setting to retrieve.
- * @param def Value to return if the setting is not defined.
- * @return The setting's current value, or 'def' if it is not defined.
- */
- private boolean getBoolean(String name, boolean def) {
- ContentValues values = getValues(name);
- return values != null ? values.getAsBoolean(VALUE) : def;
- }
-
- /**
- * Convenience function for retrieving a single settings value
- * as a String.
- *
- * @param name The name of the setting to retrieve.
- * @param def The value to return if the setting is not defined.
- * @return The setting's current value or 'def' if it is not defined.
- */
- private String getString(String name, String def) {
- ContentValues values = getValues(name);
- return values != null ? values.getAsString(VALUE) : def;
- }
-
- /**
- * Convenience function for retrieving a single settings value
- * as an Integer.
- *
- * @param name The name of the setting to retrieve.
- * @param def The value to return if the setting is not defined.
- * @return The setting's current value or 'def' if it is not defined.
- */
- private int getInteger(String name, int def) {
- ContentValues values = getValues(name);
- return values != null ? values.getAsInteger(VALUE) : def;
- }
-
- /**
- * Convenience function for retrieving a single settings value
- * as a Long.
- *
- * @param name The name of the setting to retrieve.
- * @param def The value to return if the setting is not defined.
- * @return The setting's current value or 'def' if it is not defined.
- */
- private long getLong(String name, long def) {
- ContentValues values = getValues(name);
- return values != null ? values.getAsLong(VALUE) : def;
- }
- }
-
- }
-
-
- /**
- * Columns for GTalk branding resource map cache table. This table caches the result of
- * loading the branding resources to speed up GTalk landing page start.
- */
- public interface BrandingResourceMapCacheColumns {
- /**
- * The provider ID
- * <P>Type: INTEGER</P>
- */
- String PROVIDER_ID = "provider_id";
- /**
- * The application resource ID
- * <P>Type: INTEGER</P>
- */
- String APP_RES_ID = "app_res_id";
- /**
- * The plugin resource ID
- * <P>Type: INTEGER</P>
- */
- String PLUGIN_RES_ID = "plugin_res_id";
- }
-
- /**
- * The table for caching the result of loading GTalk branding resources.
- */
- public static final class BrandingResourceMapCache
- implements BaseColumns, BrandingResourceMapCacheColumns {
- /**
- * The content:// style URL for this table.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/brandingResMapCache");
- }
-
-
-
- /**
- * //TODO: move these to MCS specific provider.
- * The following are MCS stuff, and should really live in a separate provider specific to
- * MCS code.
- */
-
- /**
- * Columns from OutgoingRmq table
- */
- public interface OutgoingRmqColumns {
- String RMQ_ID = "rmq_id";
- String TIMESTAMP = "ts";
- String DATA = "data";
- String PROTOBUF_TAG = "type";
- }
-
- /**
- * //TODO: we should really move these to their own provider and database.
- * The table for storing outgoing rmq packets.
- */
- public static final class OutgoingRmq implements BaseColumns, OutgoingRmqColumns {
- private static String[] RMQ_ID_PROJECTION = new String[] {
- RMQ_ID,
- };
-
- /**
- * queryHighestRmqId
- *
- * @param resolver the content resolver
- * @return the highest rmq id assigned to the rmq packet, or 0 if there are no rmq packets
- * in the OutgoingRmq table.
- */
- public static final long queryHighestRmqId(ContentResolver resolver) {
- Cursor cursor = resolver.query(Im.OutgoingRmq.CONTENT_URI_FOR_HIGHEST_RMQ_ID,
- RMQ_ID_PROJECTION,
- null, // selection
- null, // selection args
- null // sort
- );
-
- long retVal = 0;
- try {
- //if (DBG) log("initializeRmqid: cursor.count= " + cursor.count());
-
- if (cursor.moveToFirst()) {
- retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID));
- }
- } finally {
- cursor.close();
- }
-
- return retVal;
- }
-
- /**
- * The content:// style URL for this table.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/outgoingRmqMessages");
-
- /**
- * The content:// style URL for the highest rmq id for the outgoing rmq messages
- */
- public static final Uri CONTENT_URI_FOR_HIGHEST_RMQ_ID =
- Uri.parse("content://com.google.android.providers.talk/outgoingHighestRmqId");
-
- /**
- * The default sort order for this table.
- */
- public static final String DEFAULT_SORT_ORDER = "rmq_id ASC";
- }
-
- /**
- * Columns for the LastRmqId table, which stores a single row for the last client rmq id
- * sent to the server.
- */
- public interface LastRmqIdColumns {
- String RMQ_ID = "rmq_id";
- }
-
- /**
- * //TODO: move these out into their own provider and database
- * The table for storing the last client rmq id sent to the server.
- */
- public static final class LastRmqId implements BaseColumns, LastRmqIdColumns {
- private static String[] PROJECTION = new String[] {
- RMQ_ID,
- };
-
- /**
- * queryLastRmqId
- *
- * queries the last rmq id saved in the LastRmqId table.
- *
- * @param resolver the content resolver.
- * @return the last rmq id stored in the LastRmqId table, or 0 if not found.
- */
- public static final long queryLastRmqId(ContentResolver resolver) {
- Cursor cursor = resolver.query(Im.LastRmqId.CONTENT_URI,
- PROJECTION,
- null, // selection
- null, // selection args
- null // sort
- );
-
- long retVal = 0;
- try {
- if (cursor.moveToFirst()) {
- retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID));
- }
- } finally {
- cursor.close();
- }
-
- return retVal;
- }
-
- /**
- * saveLastRmqId
- *
- * saves the rmqId to the lastRmqId table. This will override the existing row if any,
- * as we only keep one row of data in this table.
- *
- * @param resolver the content resolver.
- * @param rmqId the rmq id to be saved.
- */
- public static final void saveLastRmqId(ContentResolver resolver, long rmqId) {
- ContentValues values = new ContentValues();
-
- // always replace the first row.
- values.put(_ID, 1);
- values.put(RMQ_ID, rmqId);
- resolver.insert(CONTENT_URI, values);
- }
-
- /**
- * The content:// style URL for this table.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/lastRmqId");
- }
-
- /**
- * Columns for the s2dRmqIds table, which stores the server-to-device message
- * persistent ids. These are used in the RMQ2 protocol, where in the login request, the
- * client selective acks these s2d ids to the server.
- */
- public interface ServerToDeviceRmqIdsColumn {
- String RMQ_ID = "rmq_id";
- }
-
- public static final class ServerToDeviceRmqIds implements BaseColumns,
- ServerToDeviceRmqIdsColumn {
-
- /**
- * The content:// style URL for this table.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/s2dids");
- }
-
-}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 062080d..1b938ee 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -96,14 +96,12 @@ public final class MediaStore {
/**
* The name of an Intent-extra used to control the UI of a ViewImage.
* This is a boolean property that overrides the activity's default fullscreen state.
- * @hide
*/
public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen";
/**
* The name of an Intent-extra used to control the UI of a ViewImage.
* This is a boolean property that specifies whether or not to show action icons.
- * @hide
*/
public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
@@ -162,13 +160,11 @@ public final class MediaStore {
/**
* Specify the maximum allowed size.
- * @hide
*/
public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
/**
* Specify the maximum allowed recording duration in seconds.
- * @hide
*/
public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
@@ -179,6 +175,13 @@ public final class MediaStore {
public final static String EXTRA_OUTPUT = "output";
/**
+ * The string that is used when a media attribute is not known. For example,
+ * if an audio file does not have any meta data, the artist and album columns
+ * will be set to this value.
+ */
+ public static final String UNKNOWN_STRING = "<unknown>";
+
+ /**
* Common fields for most MediaProvider tables
*/
@@ -238,6 +241,29 @@ public final class MediaStore {
private static final int FULL_SCREEN_KIND = 2;
private static final int MICRO_KIND = 3;
private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
+ static final int DEFAULT_GROUP_ID = 0;
+
+ private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) {
+ Bitmap bitmap = null;
+ Uri thumbUri = null;
+ try {
+ long thumbId = c.getLong(0);
+ String filePath = c.getString(1);
+ thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
+ ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r");
+ bitmap = BitmapFactory.decodeFileDescriptor(
+ pfdInput.getFileDescriptor(), null, options);
+ pfdInput.close();
+ } catch (FileNotFoundException ex) {
+ Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+ } catch (IOException ex) {
+ Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+ } catch (OutOfMemoryError ex) {
+ Log.e(TAG, "failed to allocate memory for thumbnail "
+ + thumbUri + "; " + ex);
+ }
+ return bitmap;
+ }
/**
* This method cancels the thumbnail request so clients waiting for getThumbnail will be
@@ -246,11 +272,14 @@ public final class MediaStore {
*
* @param cr ContentResolver
* @param origId original image or video id. use -1 to cancel all requests.
+ * @param groupId the same groupId used in getThumbnail
* @param baseUri the base URI of requested thumbnails
*/
- static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) {
+ static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri,
+ long groupId) {
Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1")
- .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+ .appendQueryParameter("orig_id", String.valueOf(origId))
+ .appendQueryParameter("group_id", String.valueOf(groupId)).build();
Cursor c = null;
try {
c = cr.query(cancelUri, PROJECTION, null, null, null);
@@ -271,18 +300,20 @@ public final class MediaStore {
* @param kind could be MINI_KIND or MICRO_KIND
* @param options this is only used for MINI_KIND when decoding the Bitmap
* @param baseUri the base URI of requested thumbnails
+ * @param groupId the id of group to which this request belongs
* @return Bitmap bitmap of specified thumbnail kind
*/
- static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
Bitmap bitmap = null;
String filePath = null;
// Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
- // some optimization for MICRO_KIND: if the magic is non-zero, we don't bother
+ // If the magic is non-zero, we simply return thumbnail if it does exist.
// querying MediaProvider and simply return thumbnail.
- if (kind == MICRO_KIND) {
- MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
- if (thumbFile.getMagic(origId) != 0) {
+ MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
+ long magic = thumbFile.getMagic(origId);
+ if (magic != 0) {
+ if (kind == MICRO_KIND) {
byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
@@ -291,20 +322,34 @@ public final class MediaStore {
}
}
return bitmap;
+ } else if (kind == MINI_KIND) {
+ String column = isVideo ? "video_id=" : "image_id=";
+ Cursor c = null;
+ try {
+ c = cr.query(baseUri, PROJECTION, column + origId, null, null);
+ if (c != null && c.moveToFirst()) {
+ bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
+ if (bitmap != null) {
+ return bitmap;
+ }
+ }
+ } finally {
+ if (c != null) c.close();
+ }
}
}
Cursor c = null;
try {
Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
- .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+ .appendQueryParameter("orig_id", String.valueOf(origId))
+ .appendQueryParameter("group_id", String.valueOf(groupId)).build();
c = cr.query(blockingUri, PROJECTION, null, null, null);
// This happens when original image/video doesn't exist.
if (c == null) return null;
// Assuming thumbnail has been generated, at least original image exists.
if (kind == MICRO_KIND) {
- MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
@@ -314,24 +359,7 @@ public final class MediaStore {
}
} else if (kind == MINI_KIND) {
if (c.moveToFirst()) {
- ParcelFileDescriptor pfdInput;
- Uri thumbUri = null;
- try {
- long thumbId = c.getLong(0);
- filePath = c.getString(1);
- thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
- pfdInput = cr.openFileDescriptor(thumbUri, "r");
- bitmap = BitmapFactory.decodeFileDescriptor(
- pfdInput.getFileDescriptor(), null, options);
- pfdInput.close();
- } catch (FileNotFoundException ex) {
- Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
- } catch (IOException ex) {
- Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
- } catch (OutOfMemoryError ex) {
- Log.e(TAG, "failed to allocate memory for thumbnail "
- + thumbUri + "; " + ex);
- }
+ bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
}
} else {
throw new IllegalArgumentException("Unsupported kind: " + kind);
@@ -354,7 +382,7 @@ public final class MediaStore {
}
if (isVideo) {
bitmap = ThumbnailUtil.createVideoThumbnail(filePath);
- if (kind == MICRO_KIND) {
+ if (kind == MICRO_KIND && bitmap != null) {
bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
@@ -669,7 +697,8 @@ public final class MediaStore {
* @param origId original image id
*/
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+ InternalThumbnails.DEFAULT_GROUP_ID);
}
/**
@@ -685,7 +714,39 @@ public final class MediaStore {
*/
public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
BitmapFactory.Options options) {
- return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+ return InternalThumbnails.getThumbnail(cr, origId,
+ InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+ EXTERNAL_CONTENT_URI, false);
+ }
+
+ /**
+ * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+ * interrupted and return immediately. Only the original process which made the getThumbnail
+ * requests can cancel their own requests.
+ *
+ * @param cr ContentResolver
+ * @param origId original image id
+ * @param groupId the same groupId used in getThumbnail.
+ */
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+ }
+
+ /**
+ * This method checks if the thumbnails of the specified image (origId) has been created.
+ * It will be blocked until the thumbnails are generated.
+ *
+ * @param cr ContentResolver used to dispatch queries to MediaProvider.
+ * @param origId Original image id associated with thumbnail of interest.
+ * @param groupId the id of group to which this request belongs
+ * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+ * @param options this is only used for MINI_KIND when decoding the Bitmap
+ * @return A Bitmap instance. It could be null if the original image
+ * associated with origId doesn't exist or memory is not enough.
+ */
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
EXTERNAL_CONTENT_URI, false);
}
@@ -786,7 +847,6 @@ public final class MediaStore {
* The position, in ms, playback was at when playback for this file
* was last stopped.
* <P>Type: INTEGER (long)</P>
- * @hide
*/
public static final String BOOKMARK = "bookmark";
@@ -865,7 +925,6 @@ public final class MediaStore {
/**
* Non-zero if the audio file is a podcast
* <P>Type: INTEGER (boolean)</P>
- * @hide
*/
public static final String IS_PODCAST = "is_podcast";
@@ -906,7 +965,7 @@ public final class MediaStore {
public static String keyFor(String name) {
if (name != null) {
boolean sortfirst = false;
- if (name.equals(android.media.MediaFile.UNKNOWN_STRING)) {
+ if (name.equals(UNKNOWN_STRING)) {
return "\001";
}
// Check if the first character is \001. We use this to
@@ -1194,6 +1253,27 @@ public final class MediaStore {
}
/**
+ * Convenience method to move a playlist item to a new location
+ * @param res The content resolver to use
+ * @param playlistId The numeric id of the playlist
+ * @param from The position of the item to move
+ * @param to The position to move the item to
+ * @return true on success
+ */
+ public static final boolean moveItem(ContentResolver res,
+ long playlistId, int from, int to) {
+ Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+ playlistId)
+ .buildUpon()
+ .appendEncodedPath(String.valueOf(from))
+ .appendQueryParameter("move", "true")
+ .build();
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
+ return res.update(uri, values, null, null) != 0;
+ }
+
+ /**
* The ID within the playlist.
*/
public static final String _ID = "_id";
@@ -1598,7 +1678,26 @@ public final class MediaStore {
* @param origId original video id
*/
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+ InternalThumbnails.DEFAULT_GROUP_ID);
+ }
+
+ /**
+ * This method checks if the thumbnails of the specified image (origId) has been created.
+ * It will be blocked until the thumbnails are generated.
+ *
+ * @param cr ContentResolver used to dispatch queries to MediaProvider.
+ * @param origId Original image id associated with thumbnail of interest.
+ * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+ * @param options this is only used for MINI_KIND when decoding the Bitmap
+ * @return A Bitmap instance. It could be null if the original image
+ * associated with origId doesn't exist or memory is not enough.
+ */
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId,
+ InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+ EXTERNAL_CONTENT_URI, true);
}
/**
@@ -1607,18 +1706,32 @@ public final class MediaStore {
*
* @param cr ContentResolver used to dispatch queries to MediaProvider.
* @param origId Original image id associated with thumbnail of interest.
+ * @param groupId the id of group to which this request belongs
* @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
* @param options this is only used for MINI_KIND when decoding the Bitmap
* @return A Bitmap instance. It could be null if the original image associated with
* origId doesn't exist or memory is not enough.
*/
- public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
- BitmapFactory.Options options) {
- return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
EXTERNAL_CONTENT_URI, true);
}
/**
+ * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+ * interrupted and return immediately. Only the original process which made the getThumbnail
+ * requests can cancel their own requests.
+ *
+ * @param cr ContentResolver
+ * @param origId original video id
+ * @param groupId the same groupId used in getThumbnail.
+ */
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+ }
+
+ /**
* Get the content:// style URI for the image media table on the
* given volume.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f7e55db..7db9fdc 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -38,13 +38,16 @@ import android.os.*;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AndroidException;
+import android.util.Config;
import android.util.Log;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
/**
@@ -440,6 +443,7 @@ public final class Settings {
public static final String AUTHORITY = "settings";
private static final String TAG = "Settings";
+ private static final boolean LOCAL_LOGV = Config.LOGV || false;
public static class SettingNotFoundException extends AndroidException {
public SettingNotFoundException(String msg) {
@@ -476,38 +480,59 @@ public final class Settings {
private static class NameValueCache {
private final String mVersionSystemProperty;
- private final HashMap<String, String> mValues = Maps.newHashMap();
- private long mValuesVersion = 0;
private final Uri mUri;
- NameValueCache(String versionSystemProperty, Uri uri) {
+ // Must synchronize(mValues) to access mValues and mValuesVersion.
+ private final HashMap<String, String> mValues = new HashMap<String, String>();
+ private long mValuesVersion = 0;
+
+ public NameValueCache(String versionSystemProperty, Uri uri) {
mVersionSystemProperty = versionSystemProperty;
mUri = uri;
}
- String getString(ContentResolver cr, String name) {
+ public String getString(ContentResolver cr, String name) {
long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0);
- if (mValuesVersion != newValuesVersion) {
- mValues.clear();
- mValuesVersion = newValuesVersion;
+
+ synchronized (mValues) {
+ if (mValuesVersion != newValuesVersion) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " +
+ newValuesVersion + " != cached " + mValuesVersion);
+ }
+
+ mValues.clear();
+ mValuesVersion = newValuesVersion;
+ }
+
+ if (mValues.containsKey(name)) {
+ return mValues.get(name); // Could be null, that's OK -- negative caching
+ }
}
- if (!mValues.containsKey(name)) {
- String value = null;
- Cursor c = null;
- try {
- c = cr.query(mUri, new String[] { Settings.NameValueTable.VALUE },
- Settings.NameValueTable.NAME + "=?", new String[]{name}, null);
- if (c != null && c.moveToNext()) value = c.getString(0);
+
+ Cursor c = null;
+ try {
+ c = cr.query(mUri, new String[] { Settings.NameValueTable.VALUE },
+ Settings.NameValueTable.NAME + "=?", new String[]{name}, null);
+ if (c == null) {
+ Log.w(TAG, "Can't get key " + name + " from " + mUri);
+ return null;
+ }
+
+ String value = c.moveToNext() ? c.getString(0) : null;
+ synchronized (mValues) {
mValues.put(name, value);
- } catch (SQLException e) {
- // SQL error: return null, but don't cache it.
- Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
- } finally {
- if (c != null) c.close();
+ }
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
+ name + " = " + (value == null ? "(null)" : value));
}
return value;
- } else {
- return mValues.get(name);
+ } catch (SQLException e) {
+ Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
+ return null; // Return null, but don't cache it.
+ } finally {
+ if (c != null) c.close();
}
}
}
@@ -1169,6 +1194,12 @@ public final class Settings {
public static final String VOLUME_NOTIFICATION = "volume_notification";
/**
+ * Bluetooth Headset volume. This is used internally, changing this value will
+ * not change the volume. See AudioManager.
+ */
+ public static final String VOLUME_BLUETOOTH_SCO = "volume_bluetooth_sco";
+
+ /**
* Whether the notifications should use the ring volume (value of 1) or
* a separate notification volume (value of 0). In most cases, users
* will have this enabled so the notification and ringer volumes will be
@@ -1189,7 +1220,7 @@ public final class Settings {
*/
public static final String[] VOLUME_SETTINGS = {
VOLUME_VOICE, VOLUME_SYSTEM, VOLUME_RING, VOLUME_MUSIC,
- VOLUME_ALARM, VOLUME_NOTIFICATION
+ VOLUME_ALARM, VOLUME_NOTIFICATION, VOLUME_BLUETOOTH_SCO
};
/**
@@ -1422,7 +1453,6 @@ public final class Settings {
*/
public static final String[] SETTINGS_TO_BACKUP = {
STAY_ON_WHILE_PLUGGED_IN,
- END_BUTTON_BEHAVIOR,
WIFI_SLEEP_POLICY,
WIFI_USE_STATIC_IP,
WIFI_STATIC_IP,
@@ -1447,12 +1477,14 @@ public final class Settings {
VOLUME_MUSIC,
VOLUME_ALARM,
VOLUME_NOTIFICATION,
+ VOLUME_BLUETOOTH_SCO,
VOLUME_VOICE + APPEND_FOR_LAST_AUDIBLE,
VOLUME_SYSTEM + APPEND_FOR_LAST_AUDIBLE,
VOLUME_RING + APPEND_FOR_LAST_AUDIBLE,
VOLUME_MUSIC + APPEND_FOR_LAST_AUDIBLE,
VOLUME_ALARM + APPEND_FOR_LAST_AUDIBLE,
VOLUME_NOTIFICATION + APPEND_FOR_LAST_AUDIBLE,
+ VOLUME_BLUETOOTH_SCO + APPEND_FOR_LAST_AUDIBLE,
TEXT_AUTO_REPLACE,
TEXT_AUTO_CAPS,
TEXT_AUTO_PUNCTUATE,
@@ -2420,222 +2452,9 @@ public final class Settings {
public static final String LAST_SETUP_SHOWN = "last_setup_shown";
/**
- * @hide
- */
- public static final String[] SETTINGS_TO_BACKUP = {
- ADB_ENABLED,
- ALLOW_MOCK_LOCATION,
- PARENTAL_CONTROL_ENABLED,
- PARENTAL_CONTROL_REDIRECT_URL,
- USB_MASS_STORAGE_ENABLED,
- ACCESSIBILITY_ENABLED,
- ENABLED_ACCESSIBILITY_SERVICES,
- TTS_USE_DEFAULTS,
- TTS_DEFAULT_RATE,
- TTS_DEFAULT_PITCH,
- TTS_DEFAULT_SYNTH,
- TTS_DEFAULT_LANG,
- TTS_DEFAULT_COUNTRY,
- WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
- WIFI_NUM_ALLOWED_CHANNELS,
- WIFI_NUM_OPEN_NETWORKS_KEPT,
- };
-
- /**
- * Helper method for determining if a location provider is enabled.
- * @param cr the content resolver to use
- * @param provider the location provider to query
- * @return true if the provider is enabled
- *
- * @hide
- */
- public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
- String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
- if (allowedProviders != null) {
- return (allowedProviders.equals(provider) ||
- allowedProviders.contains("," + provider + ",") ||
- allowedProviders.startsWith(provider + ",") ||
- allowedProviders.endsWith("," + provider));
- }
- return false;
- }
-
- /**
- * Thread-safe method for enabling or disabling a single location provider.
- * @param cr the content resolver to use
- * @param provider the location provider to enable or disable
- * @param enabled true if the provider should be enabled
- *
- * @hide
- */
- public static final void setLocationProviderEnabled(ContentResolver cr,
- String provider, boolean enabled) {
- // to ensure thread safety, we write the provider name with a '+' or '-'
- // and let the SettingsProvider handle it rather than reading and modifying
- // the list of enabled providers.
- if (enabled) {
- provider = "+" + provider;
- } else {
- provider = "-" + provider;
- }
- putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
- }
- }
-
- /**
- * Gservices settings, containing the network names for Google's
- * various services. This table holds simple name/addr pairs.
- * Addresses can be accessed through the getString() method.
- *
- * TODO: This should move to partner/google/... somewhere.
- *
- * @hide
- */
- public static final class Gservices extends NameValueTable {
- public static final String SYS_PROP_SETTING_VERSION = "sys.settings_gservices_version";
-
- /**
- * Intent action broadcast when the Gservices table is updated by the server.
- * This is broadcast once after settings change (so many values may have been updated).
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String CHANGED_ACTION =
- "com.google.gservices.intent.action.GSERVICES_CHANGED";
-
- /**
- * Intent action to override Gservices for testing. (Requires WRITE_GSERVICES permission.)
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String OVERRIDE_ACTION =
- "com.google.gservices.intent.action.GSERVICES_OVERRIDE";
-
- private static volatile NameValueCache mNameValueCache = null;
- private static final Object mNameValueCacheLock = new Object();
-
- /**
- * Look up a name in the database.
- * @param resolver to access the database with
- * @param name to look up in the table
- * @return the corresponding value, or null if not present
- */
- public static String getString(ContentResolver resolver, String name) {
- synchronized (mNameValueCacheLock) {
- if (mNameValueCache == null) {
- mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
- }
- return mNameValueCache.getString(resolver, name);
- }
- }
-
- /**
- * Store a name/value pair into the database.
- * @param resolver to access the database with
- * @param name to store
- * @param value to associate with the name
- * @return true if the value was set, false on database errors
- */
- public static boolean putString(ContentResolver resolver,
- String name, String value) {
- return putString(resolver, CONTENT_URI, name, value);
- }
-
- /**
- * Look up the value for name in the database, convert it to an int using Integer.parseInt
- * and return it. If it is null or if a NumberFormatException is caught during the
- * conversion then return defValue.
- */
- public static int getInt(ContentResolver resolver, String name, int defValue) {
- String valString = getString(resolver, name);
- int value;
- try {
- value = valString != null ? Integer.parseInt(valString) : defValue;
- } catch (NumberFormatException e) {
- value = defValue;
- }
- return value;
- }
-
- /**
- * Look up the value for name in the database, convert it to a long using Long.parseLong
- * and return it. If it is null or if a NumberFormatException is caught during the
- * conversion then return defValue.
- */
- public static long getLong(ContentResolver resolver, String name, long defValue) {
- String valString = getString(resolver, name);
- long value;
- try {
- value = valString != null ? Long.parseLong(valString) : defValue;
- } catch (NumberFormatException e) {
- value = defValue;
- }
- return value;
- }
-
- /**
- * Construct the content URI for a particular name/value pair,
- * useful for monitoring changes with a ContentObserver.
- * @param name to look up in the table
- * @return the corresponding content URI, or null if not present
- */
- public static Uri getUriFor(String name) {
- return getUriFor(CONTENT_URI, name);
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/gservices");
-
- /**
- * MMS - URL to use for HTTP "x-wap-profile" header
- */
- public static final String MMS_X_WAP_PROFILE_URL
- = "mms_x_wap_profile_url";
-
- /**
- * YouTube - the flag to indicate whether to use proxy
- */
- public static final String YOUTUBE_USE_PROXY
- = "youtube_use_proxy";
-
- /**
- * MMS - maximum message size in bytes for a MMS message.
- */
- public static final String MMS_MAXIMUM_MESSAGE_SIZE
- = "mms_maximum_message_size";
-
- /**
- * Event tags from the kernel event log to upload during checkin.
- */
- public static final String CHECKIN_EVENTS = "checkin_events";
-
- /**
- * Comma-separated list of service names to dump and upload during checkin.
- */
- public static final String CHECKIN_DUMPSYS_LIST = "checkin_dumpsys_list";
-
- /**
- * Comma-separated list of packages to specify for each service that is
- * dumped (currently only meaningful for user activity).
- */
- public static final String CHECKIN_PACKAGE_LIST = "checkin_package_list";
-
- /**
- * The interval (in seconds) between periodic checkin attempts.
- */
- public static final String CHECKIN_INTERVAL = "checkin_interval";
-
- /**
- * Boolean indicating if the market app should force market only checkins on
- * install/uninstall. Any non-0 value is considered true.
- */
- public static final String MARKET_FORCE_CHECKIN = "market_force_checkin";
-
- /**
* How frequently (in seconds) to check the memory status of the
* device.
+ * @hide
*/
public static final String MEMCHECK_INTERVAL = "memcheck_interval";
@@ -2643,6 +2462,7 @@ public final class Settings {
* Max frequency (in seconds) to log memory check stats, in realtime
* seconds. This allows for throttling of logs when the device is
* running for large amounts of time.
+ * @hide
*/
public static final String MEMCHECK_LOG_REALTIME_INTERVAL =
"memcheck_log_realtime_interval";
@@ -2650,6 +2470,7 @@ public final class Settings {
/**
* Boolean indicating whether rebooting due to system memory checks
* is enabled.
+ * @hide
*/
public static final String MEMCHECK_SYSTEM_ENABLED = "memcheck_system_enabled";
@@ -2657,12 +2478,14 @@ public final class Settings {
* How many bytes the system process must be below to avoid scheduling
* a soft reboot. This reboot will happen when it is next determined
* to be a good time.
+ * @hide
*/
public static final String MEMCHECK_SYSTEM_SOFT_THRESHOLD = "memcheck_system_soft";
/**
* How many bytes the system process must be below to avoid scheduling
* a hard reboot. This reboot will happen immediately.
+ * @hide
*/
public static final String MEMCHECK_SYSTEM_HARD_THRESHOLD = "memcheck_system_hard";
@@ -2670,18 +2493,21 @@ public final class Settings {
* How many bytes the phone process must be below to avoid scheduling
* a soft restart. This restart will happen when it is next determined
* to be a good time.
+ * @hide
*/
public static final String MEMCHECK_PHONE_SOFT_THRESHOLD = "memcheck_phone_soft";
/**
* How many bytes the phone process must be below to avoid scheduling
* a hard restart. This restart will happen immediately.
+ * @hide
*/
public static final String MEMCHECK_PHONE_HARD_THRESHOLD = "memcheck_phone_hard";
/**
* Boolean indicating whether restarting the phone process due to
* memory checks is enabled.
+ * @hide
*/
public static final String MEMCHECK_PHONE_ENABLED = "memcheck_phone_enabled";
@@ -2689,6 +2515,7 @@ public final class Settings {
* First time during the day it is okay to kill processes
* or reboot the device due to low memory situations. This number is
* in seconds since midnight.
+ * @hide
*/
public static final String MEMCHECK_EXEC_START_TIME = "memcheck_exec_start_time";
@@ -2696,6 +2523,7 @@ public final class Settings {
* Last time during the day it is okay to kill processes
* or reboot the device due to low memory situations. This number is
* in seconds since midnight.
+ * @hide
*/
public static final String MEMCHECK_EXEC_END_TIME = "memcheck_exec_end_time";
@@ -2703,6 +2531,7 @@ public final class Settings {
* How long the screen must have been off in order to kill processes
* or reboot. This number is in seconds. A value of -1 means to
* entirely disregard whether the screen is on.
+ * @hide
*/
public static final String MEMCHECK_MIN_SCREEN_OFF = "memcheck_min_screen_off";
@@ -2711,6 +2540,7 @@ public final class Settings {
* or reboot. This number is in seconds. Note: this value must be
* smaller than {@link #MEMCHECK_RECHECK_INTERVAL} or else it will
* always see an alarm scheduled within its time.
+ * @hide
*/
public static final String MEMCHECK_MIN_ALARM = "memcheck_min_alarm";
@@ -2720,12 +2550,14 @@ public final class Settings {
* this value must be larger than {@link #MEMCHECK_MIN_ALARM} or else
* the alarm to schedule the recheck will always appear within the
* minimum "do not execute now" time.
+ * @hide
*/
public static final String MEMCHECK_RECHECK_INTERVAL = "memcheck_recheck_interval";
/**
* How frequently (in DAYS) to reboot the device. If 0, no reboots
* will occur.
+ * @hide
*/
public static final String REBOOT_INTERVAL = "reboot_interval";
@@ -2733,6 +2565,7 @@ public final class Settings {
* First time during the day it is okay to force a reboot of the
* device (if REBOOT_INTERVAL is set). This number is
* in seconds since midnight.
+ * @hide
*/
public static final String REBOOT_START_TIME = "reboot_start_time";
@@ -2741,674 +2574,117 @@ public final class Settings {
* a reboot can be executed. If 0, a reboot will always be executed at
* exactly the given time. Otherwise, it will only be executed if
* the device is idle within the window.
+ * @hide
*/
public static final String REBOOT_WINDOW = "reboot_window";
/**
- * The minimum version of the server that is required in order for the device to accept
- * the server's recommendations about the initial sync settings to use. When this is unset,
- * blank or can't be interpreted as an integer then we will not ask the server for a
- * recommendation.
- */
- public static final String GMAIL_CONFIG_INFO_MIN_SERVER_VERSION =
- "gmail_config_info_min_server_version";
-
- /**
- * Controls whether Gmail offers a preview button for images.
- */
- public static final String GMAIL_DISALLOW_IMAGE_PREVIEWS = "gmail_disallow_image_previews";
-
- /**
- * The maximal size in bytes allowed for attachments when composing messages in Gmail
- */
- public static final String GMAIL_MAX_ATTACHMENT_SIZE = "gmail_max_attachment_size_bytes";
-
- /**
- * The timeout in milliseconds that Gmail uses when opening a connection and reading
- * from it. A missing value or a value of -1 instructs Gmail to use the defaults provided
- * by GoogleHttpClient.
- */
- public static final String GMAIL_TIMEOUT_MS = "gmail_timeout_ms";
-
- /**
- * Controls whether Gmail will request an expedited sync when a message is sent. Value must
- * be an integer where non-zero means true. Defaults to 1.
- */
- public static final String GMAIL_SEND_IMMEDIATELY = "gmail_send_immediately";
-
- /**
- * Controls whether gmail buffers server responses. Possible values are "memory", for a
- * memory-based buffer, or "file", for a temp-file-based buffer. All other values
- * (including not set) disable buffering.
- */
- public static final String GMAIL_BUFFER_SERVER_RESPONSE = "gmail_buffer_server_response";
-
- /**
- * The maximum size in bytes allowed for the provider to gzip a protocol buffer uploaded to
- * the server.
- */
- public static final String GMAIL_MAX_GZIP_SIZE = "gmail_max_gzip_size_bytes";
-
- /**
- * Controls whether Gmail will discard uphill operations that repeatedly fail. Value must be
- * an integer where non-zero means true. Defaults to 1. This flag controls Donut devices.
- */
- public static final String GMAIL_DISCARD_ERROR_UPHILL_OP = "gmail_discard_error_uphill_op";
-
- /**
- * Controls whether Gmail will discard uphill operations that repeatedly fail. Value must be
- * an integer where non-zero means true. Defaults to 1. This flag controls Eclair and
- * future devices.
- */
- public static final String GMAIL_DISCARD_ERROR_UPHILL_OP_NEW =
- "gmail_discard_error_uphill_op_new";
-
- /**
- * Controls how many attempts Gmail will try to upload an uphill operations before it
- * abandons the operation. Defaults to 20.
- */
- public static final String GMAIL_NUM_RETRY_UPHILL_OP = "gmail_num_retry_uphill_op";
-
- /**
- * How much time in seconds Gmail will try to upload an uphill operations before it
- * abandons the operation. Defaults to 36400 (one day).
- */
- public static final String GMAIL_WAIT_TIME_RETRY_UPHILL_OP =
- "gmail_wait_time_retry_uphill_op";
-
- /**
- * Controls if the protocol buffer version of the protocol will use a multipart request for
- * attachment uploads. Value must be an integer where non-zero means true. Defaults to 0.
- */
- public static final String GMAIL_USE_MULTIPART_PROTOBUF = "gmail_use_multipart_protobuf";
-
- /**
- * the transcoder URL for mobile devices.
- */
- public static final String TRANSCODER_URL = "mobile_transcoder_url";
-
- /**
- * URL that points to the privacy terms of the Google Talk service.
- */
- public static final String GTALK_TERMS_OF_SERVICE_URL = "gtalk_terms_of_service_url";
-
- /**
- * Hostname of the GTalk server.
- */
- public static final String GTALK_SERVICE_HOSTNAME = "gtalk_hostname";
-
- /**
- * Secure port of the GTalk server.
- */
- public static final String GTALK_SERVICE_SECURE_PORT = "gtalk_secure_port";
-
- /**
- * The server configurable RMQ acking interval
- */
- public static final String GTALK_SERVICE_RMQ_ACK_INTERVAL = "gtalk_rmq_ack_interval";
-
- /**
- * The minimum reconnect delay for short network outages or when the network is suspended
- * due to phone use.
- */
- public static final String GTALK_SERVICE_MIN_RECONNECT_DELAY_SHORT =
- "gtalk_min_reconnect_delay_short";
-
- /**
- * The reconnect variant range for short network outages or when the network is suspended
- * due to phone use. A random number between 0 and this constant is computed and
- * added to {@link #GTALK_SERVICE_MIN_RECONNECT_DELAY_SHORT} to form the initial reconnect
- * delay.
- */
- public static final String GTALK_SERVICE_RECONNECT_VARIANT_SHORT =
- "gtalk_reconnect_variant_short";
-
- /**
- * The minimum reconnect delay for long network outages
- */
- public static final String GTALK_SERVICE_MIN_RECONNECT_DELAY_LONG =
- "gtalk_min_reconnect_delay_long";
-
- /**
- * The reconnect variant range for long network outages. A random number between 0 and this
- * constant is computed and added to {@link #GTALK_SERVICE_MIN_RECONNECT_DELAY_LONG} to
- * form the initial reconnect delay.
- */
- public static final String GTALK_SERVICE_RECONNECT_VARIANT_LONG =
- "gtalk_reconnect_variant_long";
-
- /**
- * The maximum reconnect delay time, in milliseconds.
- */
- public static final String GTALK_SERVICE_MAX_RECONNECT_DELAY =
- "gtalk_max_reconnect_delay";
-
- /**
- * The network downtime that is considered "short" for the above calculations,
- * in milliseconds.
- */
- public static final String GTALK_SERVICE_SHORT_NETWORK_DOWNTIME =
- "gtalk_short_network_downtime";
-
- /**
- * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
- * will reset the heartbeat timer. The away heartbeat should be used when the user is
- * logged into the GTalk app, but not actively using it.
- */
- public static final String GTALK_SERVICE_AWAY_HEARTBEAT_INTERVAL_MS =
- "gtalk_heartbeat_ping_interval_ms"; // keep the string backward compatible
-
- /**
- * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
- * will reset the heartbeat timer. The active heartbeat should be used when the user is
- * actively using the GTalk app.
- */
- public static final String GTALK_SERVICE_ACTIVE_HEARTBEAT_INTERVAL_MS =
- "gtalk_active_heartbeat_ping_interval_ms";
-
- /**
- * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
- * will reset the heartbeat timer. The sync heartbeat should be used when the user isn't
- * logged into the GTalk app, but auto-sync is enabled.
- */
- public static final String GTALK_SERVICE_SYNC_HEARTBEAT_INTERVAL_MS =
- "gtalk_sync_heartbeat_ping_interval_ms";
-
- /**
- * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
- * will reset the heartbeat timer. The no sync heartbeat should be used when the user isn't
- * logged into the GTalk app, and auto-sync is not enabled.
- */
- public static final String GTALK_SERVICE_NOSYNC_HEARTBEAT_INTERVAL_MS =
- "gtalk_nosync_heartbeat_ping_interval_ms";
-
- /**
- * The maximum heartbeat interval used while on the WIFI network.
- */
- public static final String GTALK_SERVICE_WIFI_MAX_HEARTBEAT_INTERVAL_MS =
- "gtalk_wifi_max_heartbeat_ping_interval_ms";
-
- /**
- * How long we wait to receive a heartbeat ping acknowledgement (or another packet)
- * from the GTalk server, before deeming the connection dead.
- */
- public static final String GTALK_SERVICE_HEARTBEAT_ACK_TIMEOUT_MS =
- "gtalk_heartbeat_ack_timeout_ms";
-
- /**
- * How long after screen is turned off before we consider the user to be idle.
- */
- public static final String GTALK_SERVICE_IDLE_TIMEOUT_MS =
- "gtalk_idle_timeout_ms";
-
- /**
- * By default, GTalkService will always connect to the server regardless of the auto-sync
- * setting. However, if this parameter is true, then GTalkService will only connect
- * if auto-sync is enabled. Using the GTalk app will trigger the connection too.
- */
- public static final String GTALK_SERVICE_CONNECT_ON_AUTO_SYNC =
- "gtalk_connect_on_auto_sync";
-
- /**
- * GTalkService holds a wakelock while broadcasting the intent for data message received.
- * It then automatically release the wakelock after a timeout. This setting controls what
- * the timeout should be.
- */
- public static final String GTALK_DATA_MESSAGE_WAKELOCK_MS =
- "gtalk_data_message_wakelock_ms";
-
- /**
- * The socket read timeout used to control how long ssl handshake wait for reads before
- * timing out. This is needed so the ssl handshake doesn't hang for a long time in some
- * circumstances.
- */
- public static final String GTALK_SSL_HANDSHAKE_TIMEOUT_MS =
- "gtalk_ssl_handshake_timeout_ms";
-
- /**
- * Compress the gtalk stream.
- */
- public static final String GTALK_COMPRESS = "gtalk_compress";
-
- /**
- * This is the timeout for which Google Talk will send the message using bareJID. In a
- * established chat between two XMPP endpoints, Google Talk uses fullJID in the format
- * of user@domain/resource in order to send the message to the specific client. However,
- * if Google Talk hasn't received a message from that client after some time, it would
- * fall back to use the bareJID, which would broadcast the message to all clients for
- * the other user.
- */
- public static final String GTALK_USE_BARE_JID_TIMEOUT_MS = "gtalk_use_barejid_timeout_ms";
-
- /**
- * This is the threshold of retry number when there is an authentication expired failure
- * for Google Talk. In some situation, e.g. when a Google Apps account is disabled chat
- * service, the connection keeps failing. This threshold controls when we should stop
- * the retrying.
- */
- public static final String GTALK_MAX_RETRIES_FOR_AUTH_EXPIRED =
- "gtalk_max_retries_for_auth_expired";
-
- /**
- * a boolean setting indicating whether the GTalkService should use RMQ2 protocol or not.
- */
- public static final String GTALK_USE_RMQ2_PROTOCOL =
- "gtalk_use_rmq2";
-
- /**
- * a boolean setting indicating whether the GTalkService should support both RMQ and
- * RMQ2 protocols. This setting is true for the transitional period when we need to
- * support both protocols.
- */
- public static final String GTALK_SUPPORT_RMQ_AND_RMQ2_PROTOCOLS =
- "gtalk_support_rmq_and_rmq2";
-
- /**
- * a boolean setting controlling whether the rmq2 protocol will include stream ids in
- * the protobufs. This is used for debugging.
- */
- public static final String GTALK_RMQ2_INCLUDE_STREAM_ID =
- "gtalk_rmq2_include_stream_id";
-
- /**
- * when receiving a chat message from the server, the message could be an older message
- * whose "time sent" is x seconds from now. If x is significant enough, we want to flag
- * it so the UI can give it some special treatment when displaying the "time sent" for
- * it. This setting is to control what x is.
- */
- public static final String GTALK_OLD_CHAT_MESSAGE_THRESHOLD_IN_SEC =
- "gtalk_old_chat_msg_threshold_in_sec";
-
- /**
- * a setting to control the max connection history record GTalkService stores.
- */
- public static final String GTALK_MAX_CONNECTION_HISTORY_RECORDS =
- "gtalk_max_conn_history_records";
-
- /**
- * This is gdata url to lookup album and picture info from picasa web. It also controls
- * whether url scraping for picasa is enabled (NULL to disable).
- */
- public static final String GTALK_PICASA_ALBUM_URL =
- "gtalk_picasa_album_url";
-
- /**
- * This is the url to lookup picture info from flickr. It also controls
- * whether url scraping for flickr is enabled (NULL to disable).
- */
- public static final String GTALK_FLICKR_PHOTO_INFO_URL =
- "gtalk_flickr_photo_info_url";
-
- /**
- * This is the url to lookup an actual picture from flickr.
- */
- public static final String GTALK_FLICKR_PHOTO_URL =
- "gtalk_flickr_photo_url";
-
- /**
- * This is the gdata url to lookup info on a youtube video. It also controls
- * whether url scraping for youtube is enabled (NULL to disable).
- */
- public static final String GTALK_YOUTUBE_VIDEO_URL =
- "gtalk_youtube_video_url";
-
- /**
- * Enable/disable GTalk URL scraping for JPG images ("true" to enable).
- */
- public static final String GTALK_URL_SCRAPING_FOR_JPG =
- "gtalk_url_scraping_for_jpg";
-
- /**
- * Chat message lifetime (for pruning old chat messages).
- */
- public static final String GTALK_CHAT_MESSAGE_LIFETIME =
- "gtalk_chat_message_lifetime";
-
- /**
- * OTR message lifetime (for pruning old otr messages).
- */
- public static final String GTALK_OTR_MESSAGE_LIFETIME =
- "gtalk_otr_message_lifetime";
-
- /**
- * Chat expiration time, i.e., time since last message in the chat (for pruning old chats).
- */
- public static final String GTALK_CHAT_EXPIRATION_TIME =
- "gtalk_chat_expiration_time";
-
- /**
- * This is the url for getting the app token for server-to-device push messaging.
- */
- public static final String PUSH_MESSAGING_REGISTRATION_URL =
- "push_messaging_registration_url";
-
- /**
- * Use android://&lt;it&gt; routing infos for Google Sync Server subcriptions.
- */
- public static final String GSYNC_USE_RMQ2_ROUTING_INFO = "gsync_use_rmq2_routing_info";
-
- /**
- * Enable use of ssl session caching.
- * 'db' - save each session in a (per process) database
- * 'file' - save each session in a (per process) file
- * not set or any other value - normal java in-memory caching
- */
- public static final String SSL_SESSION_CACHE = "ssl_session_cache";
-
- /**
- * How many bytes long a message has to be, in order to be gzipped.
- */
- public static final String SYNC_MIN_GZIP_BYTES =
- "sync_min_gzip_bytes";
-
- /**
- * The hash value of the current provisioning settings
- */
- public static final String PROVISIONING_DIGEST = "digest";
-
- /**
- * Provisioning keys to block from server update
- */
- public static final String PROVISIONING_OVERRIDE = "override";
-
- /**
- * "Generic" service name for authentication requests.
- */
- public static final String GOOGLE_LOGIN_GENERIC_AUTH_SERVICE
- = "google_login_generic_auth_service";
-
- /**
- * Frequency in milliseconds at which we should sync the locally installed Vending Machine
- * content with the server.
- */
- public static final String VENDING_SYNC_FREQUENCY_MS = "vending_sync_frequency_ms";
-
- /**
- * Support URL that is opened in a browser when user clicks on 'Help and Info' in Vending
- * Machine.
- */
- public static final String VENDING_SUPPORT_URL = "vending_support_url";
-
- /**
- * Indicates if Vending Machine requires a SIM to be in the phone to allow a purchase.
- *
- * true = SIM is required
- * false = SIM is not required
- */
- public static final String VENDING_REQUIRE_SIM_FOR_PURCHASE =
- "vending_require_sim_for_purchase";
-
- /**
- * Indicates the Vending Machine backup state. It is set if the
- * Vending application has been backed up at least once.
- */
- public static final String VENDING_BACKUP_STATE = "vending_backup_state";
-
- /**
- * The current version id of the Vending Machine terms of service.
- */
- public static final String VENDING_TOS_VERSION = "vending_tos_version";
-
- /**
- * URL that points to the terms of service for Vending Machine.
- */
- public static final String VENDING_TOS_URL = "vending_tos_url";
-
- /**
- * URL to navigate to in browser (not Market) when the terms of service
- * for Vending Machine could not be accessed due to bad network
- * connection.
- */
- public static final String VENDING_TOS_MISSING_URL = "vending_tos_missing_url";
-
- /**
- * Whether to use sierraqa instead of sierra tokens for the purchase flow in
- * Vending Machine.
- *
- * true = use sierraqa
- * false = use sierra (default)
- */
- public static final String VENDING_USE_CHECKOUT_QA_SERVICE =
- "vending_use_checkout_qa_service";
-
- /**
- * Default value to use for all/free/priced filter in Market.
- * Valid values: ALL, FREE, PAID (case insensitive)
- */
- public static final String VENDING_DEFAULT_FILTER = "vending_default_filter";
- /**
- * Ranking type value to use for the first category tab (currently popular)
- */
- public static final String VENDING_TAB_1_RANKING_TYPE = "vending_tab_1_ranking_type";
-
- /**
- * Title string to use for first category tab.
- */
- public static final String VENDING_TAB_1_TITLE = "vending_tab_1_title";
-
- /**
- * Ranking type value to use for the second category tab (currently newest)
- */
- public static final String VENDING_TAB_2_RANKING_TYPE = "vending_tab_2_ranking_type";
-
- /**
- * Title string to use for second category tab.
- */
- public static final String VENDING_TAB_2_TITLE = "vending_tab_2_title";
-
- /**
- * Frequency in milliseconds at which we should request MCS heartbeats
- * from the Vending Machine client.
- */
- public static final String VENDING_HEARTBEAT_FREQUENCY_MS =
- "vending_heartbeat_frequency_ms";
-
- /**
- * Frequency in milliseconds at which we should resend pending download
- * requests to the API Server from the Vending Machine client.
- */
- public static final String VENDING_PENDING_DOWNLOAD_RESEND_FREQUENCY_MS =
- "vending_pd_resend_frequency_ms";
-
- /**
- * Time before an asset in the 'DOWNLOADING' state is considered ready
- * for an install kick on the client.
- */
- public static final String VENDING_DOWNLOADING_KICK_TIMEOUT_MS =
- "vending_downloading_kick_ms";
-
- /**
- * Size of buffer in bytes for Vending to use when reading cache files.
- */
- public static final String VENDING_DISK_INPUT_BUFFER_BYTES =
- "vending_disk_input_buffer_bytes";
-
- /**
- * Size of buffer in bytes for Vending to use when writing cache files.
- */
- public static final String VENDING_DISK_OUTPUT_BUFFER_BYTES =
- "vending_disk_output_buffer_bytes";
-
- /**
- * Frequency in milliseconds at which we should cycle through the promoted applications
- * on the home screen or the categories page.
- */
- public static final String VENDING_PROMO_REFRESH_FREQUENCY_MS =
- "vending_promo_refresh_freq_ms";
-
- /**
- * Frequency in milliseconds when we should refresh the provisioning information from
- * the carrier backend.
- */
- public static final String VENDING_CARRIER_PROVISIONING_REFRESH_FREQUENCY_MS =
- "vending_carrier_ref_freq_ms";
-
- /**
- * Interval in milliseconds after which a failed provisioning request should be retried.
- */
- public static final String VENDING_CARRIER_PROVISIONING_RETRY_MS =
- "vending_carrier_prov_retry_ms";
-
- /**
- * Buffer in milliseconds for carrier credentials to be considered valid.
- */
- public static final String VENDING_CARRIER_CREDENTIALS_BUFFER_MS =
- "vending_carrier_cred_buf_ms";
-
- /**
- * URL that points to the legal terms of service to display in Settings.
- * <p>
- * This should be a https URL. For a pretty user-friendly URL, use
- * {@link #SETTINGS_TOS_PRETTY_URL}.
- */
- public static final String SETTINGS_TOS_URL = "settings_tos_url";
-
- /**
- * URL that points to the legal terms of service to display in Settings.
- * <p>
- * This should be a pretty http URL. For the URL the device will access
- * via Settings, use {@link #SETTINGS_TOS_URL}.
- */
- public static final String SETTINGS_TOS_PRETTY_URL = "settings_tos_pretty_url";
-
- /**
- * URL that points to the contributors to display in Settings.
- * <p>
- * This should be a https URL. For a pretty user-friendly URL, use
- * {@link #SETTINGS_CONTRIBUTORS_PRETTY_URL}.
- */
- public static final String SETTINGS_CONTRIBUTORS_URL = "settings_contributors_url";
-
- /**
- * URL that points to the contributors to display in Settings.
- * <p>
- * This should be a pretty http URL. For the URL the device will access
- * via Settings, use {@link #SETTINGS_CONTRIBUTORS_URL}.
+ * Threshold values for the duration and level of a discharge cycle, under
+ * which we log discharge cycle info.
+ * @hide
*/
- public static final String SETTINGS_CONTRIBUTORS_PRETTY_URL =
- "settings_contributors_pretty_url";
+ public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
+ "battery_discharge_duration_threshold";
+ /** @hide */
+ public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
/**
- * URL that points to the Terms Of Service for the device.
- * <p>
- * This should be a pretty http URL.
+ * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
+ * on application crashes and ANRs. If this is disabled, the crash/ANR dialog
+ * will never display the "Report" button.
+ * Type: int ( 0 = disallow, 1 = allow )
+ * @hide
*/
- public static final String SETUP_GOOGLE_TOS_URL = "setup_google_tos_url";
+ public static final String SEND_ACTION_APP_ERROR = "send_action_app_error";
/**
- * URL that points to the Android privacy policy for the device.
- * <p>
- * This should be a pretty http URL.
+ * Nonzero causes Log.wtf() to crash.
+ * @hide
*/
- public static final String SETUP_ANDROID_PRIVACY_URL = "setup_android_privacy_url";
+ public static final String WTF_IS_FATAL = "wtf_is_fatal";
/**
- * URL that points to the Google privacy policy for the device.
- * <p>
- * This should be a pretty http URL.
+ * Maximum age of entries kept by {@link android.os.IDropBox}.
+ * @hide
*/
- public static final String SETUP_GOOGLE_PRIVACY_URL = "setup_google_privacy_url";
-
+ public static final String DROPBOX_AGE_SECONDS =
+ "dropbox_age_seconds";
/**
- * Request an MSISDN token for various Google services.
+ * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what.
+ * @hide
*/
- public static final String USE_MSISDN_TOKEN = "use_msisdn_token";
-
+ public static final String DROPBOX_QUOTA_KB =
+ "dropbox_quota_kb";
/**
- * RSA public key used to encrypt passwords stored in the database.
+ * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use.
+ * @hide
*/
- public static final String GLS_PUBLIC_KEY = "google_login_public_key";
-
+ public static final String DROPBOX_QUOTA_PERCENT =
+ "dropbox_quota_percent";
/**
- * Only check parental control status if this is set to "true".
+ * Percent of total disk which {@link android.os.IDropBox} will never dip into.
+ * @hide
*/
- public static final String PARENTAL_CONTROL_CHECK_ENABLED =
- "parental_control_check_enabled";
-
+ public static final String DROPBOX_RESERVE_PERCENT =
+ "dropbox_reserve_percent";
/**
- * The list of applications we need to block if parental control is
- * enabled.
+ * Prefix for per-tag dropbox disable/enable settings.
+ * @hide
*/
- public static final String PARENTAL_CONTROL_APPS_LIST =
- "parental_control_apps_list";
+ public static final String DROPBOX_TAG_PREFIX =
+ "dropbox:";
- /**
- * Duration in which parental control status is valid.
- */
- public static final String PARENTAL_CONTROL_TIMEOUT_IN_MS =
- "parental_control_timeout_in_ms";
/**
- * When parental control is off, we expect to get this string from the
- * litmus url.
+ * Screen timeout in milliseconds corresponding to the
+ * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest
+ * possible screen timeout behavior.)
+ * @hide
*/
- public static final String PARENTAL_CONTROL_EXPECTED_RESPONSE =
- "parental_control_expected_response";
+ public static final String SHORT_KEYLIGHT_DELAY_MS =
+ "short_keylight_delay_ms";
/**
- * When the litmus url returns a 302, declare parental control to be on
- * only if the redirect url matches this regular expression.
+ * The interval in minutes after which the amount of free storage left on the
+ * device is logged to the event log
+ * @hide
*/
- public static final String PARENTAL_CONTROL_REDIRECT_REGEX =
- "parental_control_redirect_regex";
+ public static final String SYS_FREE_STORAGE_LOG_INTERVAL =
+ "sys_free_storage_log_interval";
/**
* Threshold for the amount of change in disk free space required to report the amount of
* free space. Used to prevent spamming the logs when the disk free space isn't changing
* frequently.
+ * @hide
*/
public static final String DISK_FREE_CHANGE_REPORTING_THRESHOLD =
"disk_free_change_reporting_threshold";
- /**
- * Prefix for new Google services published by the checkin
- * server.
- */
- public static final String GOOGLE_SERVICES_PREFIX
- = "google_services:";
-
- /**
- * The maximum reconnect delay for short network outages or when the network is suspended
- * due to phone use.
- */
- public static final String SYNC_MAX_RETRY_DELAY_IN_SECONDS =
- "sync_max_retry_delay_in_seconds";
/**
* Minimum percentage of free storage on the device that is used to determine if
* the device is running low on storage.
* Say this value is set to 10, the device is considered running low on storage
* if 90% or more of the device storage is filled up.
+ * @hide
*/
public static final String SYS_STORAGE_THRESHOLD_PERCENTAGE =
"sys_storage_threshold_percentage";
/**
- * The interval in minutes after which the amount of free storage left on the
- * device is logged to the event log
- */
- public static final String SYS_FREE_STORAGE_LOG_INTERVAL =
- "sys_free_storage_log_interval";
-
- /**
- * The interval in milliseconds at which to check the number of SMS sent
- * out without asking for use permit, to limit the un-authorized SMS
- * usage.
- */
- public static final String SMS_OUTGOING_CHECK_INTERVAL_MS =
- "sms_outgoing_check_interval_ms";
-
- /**
- * The number of outgoing SMS sent without asking for user permit
- * (of {@link #SMS_OUTGOING_CHECK_INTERVAL_MS}
+ * The interval in milliseconds after which Wi-Fi is considered idle.
+ * When idle, it is possible for the device to be switched from Wi-Fi to
+ * the mobile data network.
+ * @hide
*/
- public static final String SMS_OUTGOING_CEHCK_MAX_COUNT =
- "sms_outgoing_check_max_count";
+ public static final String WIFI_IDLE_MS = "wifi_idle_ms";
/**
* The interval in milliseconds at which to check packet counts on the
* mobile data interface when screen is on, to detect possible data
* connection problems.
+ * @hide
*/
public static final String PDP_WATCHDOG_POLL_INTERVAL_MS =
"pdp_watchdog_poll_interval_ms";
@@ -3417,6 +2693,7 @@ public final class Settings {
* The interval in milliseconds at which to check packet counts on the
* mobile data interface when screen is off, to detect possible data
* connection problems.
+ * @hide
*/
public static final String PDP_WATCHDOG_LONG_POLL_INTERVAL_MS =
"pdp_watchdog_long_poll_interval_ms";
@@ -3425,6 +2702,7 @@ public final class Settings {
* The interval in milliseconds at which to check packet counts on the
* mobile data interface after {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT}
* outgoing packets has been reached without incoming packets.
+ * @hide
*/
public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS =
"pdp_watchdog_error_poll_interval_ms";
@@ -3433,6 +2711,7 @@ public final class Settings {
* The number of outgoing packets sent without seeing an incoming packet
* that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT}
* device is logged to the event log
+ * @hide
*/
public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT =
"pdp_watchdog_trigger_packet_count";
@@ -3441,6 +2720,7 @@ public final class Settings {
* The number of polls to perform (at {@link #PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS})
* after hitting {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} before
* attempting data connection recovery.
+ * @hide
*/
public static final String PDP_WATCHDOG_ERROR_POLL_COUNT =
"pdp_watchdog_error_poll_count";
@@ -3448,6 +2728,7 @@ public final class Settings {
/**
* The number of failed PDP reset attempts before moving to something more
* drastic: re-registering to the network.
+ * @hide
*/
public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT =
"pdp_watchdog_max_pdp_reset_fail_count";
@@ -3455,12 +2736,14 @@ public final class Settings {
/**
* Address to ping as a last sanity check before attempting any recovery.
* Unset or set to "0.0.0.0" to skip this check.
+ * @hide
*/
public static final String PDP_WATCHDOG_PING_ADDRESS = "pdp_watchdog_ping_address";
/**
* The "-w deadline" parameter for the ping, ie, the max time in
* seconds to spend pinging.
+ * @hide
*/
public static final String PDP_WATCHDOG_PING_DEADLINE = "pdp_watchdog_ping_deadline";
@@ -3469,221 +2752,269 @@ public final class Settings {
* after the first registration mismatch of gprs and voice service,
* to detect possible data network registration problems.
*
+ * @hide
*/
public static final String GPRS_REGISTER_CHECK_PERIOD_MS =
"gprs_register_check_period_ms";
/**
- * The interval in milliseconds after which Wi-Fi is considered idle.
- * When idle, it is possible for the device to be switched from Wi-Fi to
- * the mobile data network.
- */
- public static final String WIFI_IDLE_MS = "wifi_idle_ms";
-
- /**
- * Screen timeout in milliseconds corresponding to the
- * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest
- * possible screen timeout behavior.)
- */
- public static final String SHORT_KEYLIGHT_DELAY_MS =
- "short_keylight_delay_ms";
-
- /**
- * List of test suites (local disk filename) for the automatic instrumentation test runner.
- * The file format is similar to automated_suites.xml, see AutoTesterService.
- * If this setting is missing or empty, the automatic test runner will not start.
- */
- public static final String AUTOTEST_SUITES_FILE = "autotest_suites_file";
-
- /**
- * Interval between synchronous checkins forced by the automatic test runner.
- * If you set this to a value smaller than CHECKIN_INTERVAL, then the test runner's
- * frequent checkins will prevent asynchronous background checkins from interfering
- * with any performance measurements.
+ * The length of time in milli-seconds that automatic small adjustments to
+ * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
+ * @hide
*/
- public static final String AUTOTEST_CHECKIN_SECONDS = "autotest_checkin_seconds";
+ public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
/**
- * Interval between reboots forced by the automatic test runner.
+ * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
+ * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
+ * exceeded.
+ * @hide
*/
- public static final String AUTOTEST_REBOOT_SECONDS = "autotest_reboot_seconds";
-
+ public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
/**
- * Threshold values for the duration and level of a discharge cycle, under
- * which we log discharge cycle info.
+ * The maximum reconnect delay for short network outages or when the network is suspended
+ * due to phone use.
+ * @hide
*/
- public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
- "battery_discharge_duration_threshold";
- public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
+ public static final String SYNC_MAX_RETRY_DELAY_IN_SECONDS =
+ "sync_max_retry_delay_in_seconds";
/**
- * An email address that anr bugreports should be sent to.
+ * The interval in milliseconds at which to check the number of SMS sent
+ * out without asking for use permit, to limit the un-authorized SMS
+ * usage.
+ * @hide
*/
- public static final String ANR_BUGREPORT_RECIPIENT = "anr_bugreport_recipient";
+ public static final String SMS_OUTGOING_CHECK_INTERVAL_MS =
+ "sms_outgoing_check_interval_ms";
/**
- * Flag for allowing service provider to use location information to improve products and
- * services.
- * Type: int ( 0 = disallow, 1 = allow )
- * @deprecated
+ * The number of outgoing SMS sent without asking for user permit
+ * (of {@link #SMS_OUTGOING_CHECK_INTERVAL_MS}
+ * @hide
*/
- public static final String USE_LOCATION_FOR_SERVICES = "use_location";
+ public static final String SMS_OUTGOING_CHECK_MAX_COUNT =
+ "sms_outgoing_check_max_count";
/**
- * The length of the calendar sync window into the future.
- * This specifies the number of days into the future for the sliding window sync.
- * Setting this to zero will disable sliding sync.
+ * Enable use of ssl session caching.
+ * 'db' - save each session in a (per process) database
+ * 'file' - save each session in a (per process) file
+ * not set or any other value - normal java in-memory caching
+ * @hide
*/
- public static final String GOOGLE_CALENDAR_SYNC_WINDOW_DAYS =
- "google_calendar_sync_window_days";
+ public static final String SSL_SESSION_CACHE = "ssl_session_cache";
/**
- * How often to update the calendar sync window.
- * The window will be advanced every n days.
+ * How many bytes long a message has to be, in order to be gzipped.
+ * @hide
*/
- public static final String GOOGLE_CALENDAR_SYNC_WINDOW_UPDATE_DAYS =
- "google_calendar_sync_window_update_days";
+ public static final String SYNC_MIN_GZIP_BYTES =
+ "sync_min_gzip_bytes";
/**
* The number of promoted sources in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_NUM_PROMOTED_SOURCES = "search_num_promoted_sources";
/**
* The maximum number of suggestions returned by GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MAX_RESULTS_TO_DISPLAY = "search_max_results_to_display";
/**
* The number of suggestions GlobalSearch will ask each non-web search source for.
+ * @hide
*/
public static final String SEARCH_MAX_RESULTS_PER_SOURCE = "search_max_results_per_source";
/**
* The number of suggestions the GlobalSearch will ask the web search source for.
+ * @hide
*/
public static final String SEARCH_WEB_RESULTS_OVERRIDE_LIMIT =
"search_web_results_override_limit";
/**
* The number of milliseconds that GlobalSearch will wait for suggestions from
* promoted sources before continuing with all other sources.
+ * @hide
*/
public static final String SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS =
"search_promoted_source_deadline_millis";
/**
* The number of milliseconds before GlobalSearch aborts search suggesiton queries.
+ * @hide
*/
public static final String SEARCH_SOURCE_TIMEOUT_MILLIS = "search_source_timeout_millis";
/**
* The maximum number of milliseconds that GlobalSearch shows the previous results
* after receiving a new query.
+ * @hide
*/
public static final String SEARCH_PREFILL_MILLIS = "search_prefill_millis";
/**
* The maximum age of log data used for shortcuts in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MAX_STAT_AGE_MILLIS = "search_max_stat_age_millis";
/**
* The maximum age of log data used for source ranking in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS =
"search_max_source_event_age_millis";
/**
* The minimum number of impressions needed to rank a source in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING =
"search_min_impressions_for_source_ranking";
/**
* The minimum number of clicks needed to rank a source in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING =
"search_min_clicks_for_source_ranking";
/**
* The maximum number of shortcuts shown by GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MAX_SHORTCUTS_RETURNED = "search_max_shortcuts_returned";
/**
* The size of the core thread pool for suggestion queries in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_QUERY_THREAD_CORE_POOL_SIZE =
"search_query_thread_core_pool_size";
/**
* The maximum size of the thread pool for suggestion queries in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_QUERY_THREAD_MAX_POOL_SIZE =
"search_query_thread_max_pool_size";
/**
* The size of the core thread pool for shortcut refreshing in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE =
"search_shortcut_refresh_core_pool_size";
/**
* The maximum size of the thread pool for shortcut refreshing in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE =
"search_shortcut_refresh_max_pool_size";
/**
* The maximun time that excess threads in the GlobalSeach thread pools will
* wait before terminating.
+ * @hide
*/
public static final String SEARCH_THREAD_KEEPALIVE_SECONDS =
"search_thread_keepalive_seconds";
/**
* The maximum number of concurrent suggestion queries to each source.
+ * @hide
*/
public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT =
"search_per_source_concurrent_query_limit";
/**
- * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
- * on application crashes and ANRs. If this is disabled, the crash/ANR dialog
- * will never display the "Report" button.
- * Type: int ( 0 = disallow, 1 = allow )
+ * Whether or not alert sounds are played on MountService events. (0 = false, 1 = true)
+ * @hide
*/
- public static final String SEND_ACTION_APP_ERROR = "send_action_app_error";
+ public static final String MOUNT_PLAY_NOTIFICATION_SND = "mount_play_not_snd";
/**
- * Maximum size of /proc/last_kmsg content to upload after reboot.
+ * Whether or not UMS auto-starts on UMS host detection. (0 = false, 1 = true)
+ * @hide
*/
- public static final String LAST_KMSG_KB = "last_kmsg_kb";
+ public static final String MOUNT_UMS_AUTOSTART = "mount_ums_autostart";
/**
- * The length of time in milli-seconds that automatic small adjustments to
- * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
+ * Whether or not a notification is displayed on UMS host detection. (0 = false, 1 = true)
+ * @hide
*/
- public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
+ public static final String MOUNT_UMS_PROMPT = "mount_ums_prompt";
/**
- * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
- * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
- * exceeded.
+ * Whether or not a notification is displayed while UMS is enabled. (0 = false, 1 = true)
+ * @hide
*/
- public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
+ public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled";
/**
- * @deprecated
+ * If nonzero, ANRs in invisible background processes bring up a dialog.
+ * Otherwise, the process will be silently killed.
* @hide
*/
- @Deprecated // Obviated by NameValueCache: just fetch the value directly.
- public static class QueryMap extends ContentQueryMap {
+ public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
- public QueryMap(ContentResolver contentResolver, Cursor cursor, boolean keepUpdated,
- Handler handlerForUpdateNotifications) {
- super(cursor, NAME, keepUpdated, handlerForUpdateNotifications);
- }
+ /**
+ * @hide
+ */
+ public static final String[] SETTINGS_TO_BACKUP = {
+ ADB_ENABLED,
+ ALLOW_MOCK_LOCATION,
+ PARENTAL_CONTROL_ENABLED,
+ PARENTAL_CONTROL_REDIRECT_URL,
+ USB_MASS_STORAGE_ENABLED,
+ ACCESSIBILITY_ENABLED,
+ ENABLED_ACCESSIBILITY_SERVICES,
+ TTS_USE_DEFAULTS,
+ TTS_DEFAULT_RATE,
+ TTS_DEFAULT_PITCH,
+ TTS_DEFAULT_SYNTH,
+ TTS_DEFAULT_LANG,
+ TTS_DEFAULT_COUNTRY,
+ WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+ WIFI_NUM_ALLOWED_CHANNELS,
+ WIFI_NUM_OPEN_NETWORKS_KEPT,
+ MOUNT_PLAY_NOTIFICATION_SND,
+ MOUNT_UMS_AUTOSTART,
+ MOUNT_UMS_PROMPT,
+ MOUNT_UMS_NOTIFY_ENABLED
+ };
- public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
- Handler handlerForUpdateNotifications) {
- this(contentResolver,
- contentResolver.query(CONTENT_URI, null, null, null, null),
- keepUpdated, handlerForUpdateNotifications);
+ /**
+ * Helper method for determining if a location provider is enabled.
+ * @param cr the content resolver to use
+ * @param provider the location provider to query
+ * @return true if the provider is enabled
+ *
+ * @hide
+ */
+ public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
+ String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
+ if (allowedProviders != null) {
+ return (allowedProviders.equals(provider) ||
+ allowedProviders.contains("," + provider + ",") ||
+ allowedProviders.startsWith(provider + ",") ||
+ allowedProviders.endsWith("," + provider));
}
+ return false;
+ }
- public String getString(String name) {
- ContentValues cv = getValues(name);
- if (cv == null) return null;
- return cv.getAsString(VALUE);
+ /**
+ * Thread-safe method for enabling or disabling a single location provider.
+ * @param cr the content resolver to use
+ * @param provider the location provider to enable or disable
+ * @param enabled true if the provider should be enabled
+ *
+ * @hide
+ */
+ public static final void setLocationProviderEnabled(ContentResolver cr,
+ String provider, boolean enabled) {
+ // to ensure thread safety, we write the provider name with a '+' or '-'
+ // and let the SettingsProvider handle it rather than reading and modifying
+ // the list of enabled providers.
+ if (enabled) {
+ provider = "+" + provider;
+ } else {
+ provider = "-" + provider;
}
+ putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
}
-
}
/**
diff --git a/core/java/android/provider/SubscribedFeeds.java b/core/java/android/provider/SubscribedFeeds.java
deleted file mode 100644
index 8e9f402..0000000
--- a/core/java/android/provider/SubscribedFeeds.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.provider;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.accounts.Account;
-
-/**
- * The SubscribedFeeds provider stores all information about subscribed feeds.
- *
- * @hide
- */
-public class SubscribedFeeds {
- private SubscribedFeeds() {}
-
- /**
- * Columns from the Feed table that other tables join into themselves.
- */
- public interface FeedColumns {
- /**
- * The feed url.
- * <P>Type: TEXT</P>
- */
- public static final String FEED = "feed";
-
- /**
- * The authority that cares about the feed.
- * <P>Type: TEXT</P>
- */
- public static final String AUTHORITY = "authority";
-
- /**
- * The gaia service this feed is for (used for authentication).
- * <P>Type: TEXT</P>
- */
- public static final String SERVICE = "service";
- }
-
- /**
- * Provides constants to access the Feeds table and some utility methods
- * to ease using the Feeds content provider.
- */
- public static final class Feeds implements BaseColumns, SyncConstValue,
- FeedColumns {
- private Feeds() {}
-
- public static Cursor query(ContentResolver cr, String[] projection) {
- return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
- }
-
- public static Cursor query(ContentResolver cr, String[] projection,
- String where, String[] whereArgs, String orderBy) {
- return cr.query(CONTENT_URI, projection, where,
- whereArgs, (orderBy == null) ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://subscribedfeeds/feeds");
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri DELETED_CONTENT_URI =
- Uri.parse("content://subscribedfeeds/deleted_feeds");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * subscribed feeds.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/subscribedfeeds";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * subscribed feed.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/subscribedfeed";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT_TYPE, _SYNC_ACCOUNT ASC";
- }
-
- /**
- * A convenience method to add a feed to the SubscribedFeeds
- * content provider. The user specifies the values of the FEED,
- * _SYNC_ACCOUNT, AUTHORITY. SERVICE, and ROUTING_INFO.
- * @param resolver used to access the underlying content provider
- * @param feed corresponds to the FEED column
- * @param account corresponds to the _SYNC_ACCOUNT column
- * @param authority corresponds to the AUTHORITY column
- * @param service corresponds to the SERVICE column
- * @return the Uri of the feed that was added
- */
- public static Uri addFeed(ContentResolver resolver,
- String feed, Account account,
- String authority, String service) {
- ContentValues values = new ContentValues();
- values.put(SubscribedFeeds.Feeds.FEED, feed);
- values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT, account.name);
- values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE, account.type);
- values.put(SubscribedFeeds.Feeds.AUTHORITY, authority);
- values.put(SubscribedFeeds.Feeds.SERVICE, service);
- return resolver.insert(SubscribedFeeds.Feeds.CONTENT_URI, values);
- }
-
- public static int deleteFeed(ContentResolver resolver,
- String feed, Account account, String authority) {
- StringBuilder where = new StringBuilder();
- where.append(SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds.FEED + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds.AUTHORITY + "=?");
- return resolver.delete(SubscribedFeeds.Feeds.CONTENT_URI,
- where.toString(), new String[] {account.name, account.type, feed, authority});
- }
-
- public static int deleteFeeds(ContentResolver resolver,
- Account account, String authority) {
- StringBuilder where = new StringBuilder();
- where.append(SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds.AUTHORITY + "=?");
- return resolver.delete(SubscribedFeeds.Feeds.CONTENT_URI,
- where.toString(), new String[] {account.name, account.type, authority});
- }
-
- /**
- * Columns from the Accounts table.
- */
- public interface AccountColumns {
- /**
- * The account.
- * <P>Type: TEXT</P>
- */
- public static final String _SYNC_ACCOUNT = SyncConstValue._SYNC_ACCOUNT;
-
- /**
- * The account type.
- * <P>Type: TEXT</P>
- */
- public static final String _SYNC_ACCOUNT_TYPE = SyncConstValue._SYNC_ACCOUNT_TYPE;
- }
-
- /**
- * Provides constants to access the Accounts table and some utility methods
- * to ease using it.
- */
- public static final class Accounts implements BaseColumns, AccountColumns {
- private Accounts() {}
-
- public static Cursor query(ContentResolver cr, String[] projection) {
- return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
- }
-
- public static Cursor query(ContentResolver cr, String[] projection,
- String where, String orderBy) {
- return cr.query(CONTENT_URI, projection, where,
- null, (orderBy == null) ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://subscribedfeeds/accounts");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * accounts that have subscribed feeds.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/subscribedfeedaccounts";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * account in the subscribed feeds.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/subscribedfeedaccount";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT_TYPE, _SYNC_ACCOUNT ASC";
- }
-}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d8c5a53..4860cbd 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -28,10 +28,11 @@ import android.database.Cursor;
import android.net.Uri;
import android.telephony.SmsMessage;
import android.text.TextUtils;
-import android.text.util.Regex;
import android.util.Config;
import android.util.Log;
+import com.android.common.Patterns;
+
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
@@ -152,6 +153,12 @@ public final class Telephony {
* <P>Type: INTEGER (boolean)</P>
*/
public static final String LOCKED = "locked";
+
+ /**
+ * Error code associated with sending or receiving this message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "error_code";
}
/**
@@ -243,7 +250,7 @@ public final class Telephony {
* @return true if the operation succeeded
*/
public static boolean moveMessageToFolder(Context context,
- Uri uri, int folder) {
+ Uri uri, int folder, int error) {
if (uri == null) {
return false;
}
@@ -266,7 +273,7 @@ public final class Telephony {
return false;
}
- ContentValues values = new ContentValues(2);
+ ContentValues values = new ContentValues(3);
values.put(TYPE, folder);
if (markAsUnread) {
@@ -274,6 +281,7 @@ public final class Telephony {
} else if (markAsRead) {
values.put(READ, Integer.valueOf(1));
}
+ values.put(ERROR_CODE, error);
return 1 == SqliteWrapper.update(context, context.getContentResolver(),
uri, values, null, null);
@@ -545,7 +553,8 @@ public final class Telephony {
* <li><em>transactionId (Integer)</em> - The WAP transaction
* ID</li>
* <li><em>pduType (Integer)</em> - The WAP PDU type</li>
- * <li><em>data</em> - The data payload of the message</li>
+ * <li><em>header (byte[])</em> - The header of the message</li>
+ * <li><em>data (byte[])</em> - The data payload of the message</li>
* </ul>
*
* <p>If a BroadcastReceiver encounters an error while processing
@@ -1283,7 +1292,7 @@ public final class Telephony {
}
String s = extractAddrSpec(address);
- Matcher match = Regex.EMAIL_ADDRESS_PATTERN.matcher(s);
+ Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
return match.matches();
}
@@ -1298,7 +1307,7 @@ public final class Telephony {
return false;
}
- Matcher match = Regex.PHONE_PATTERN.matcher(number);
+ Matcher match = Patterns.PHONE.matcher(number);
return match.matches();
}
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 1742e72..22bb43c 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -306,7 +306,11 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return false;
// State is DISCONNECTED
+ handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
+
if (!connectSinkNative(path)) {
+ // Restore previous state
+ handleSinkStateChange(device, mAudioDevices.get(device), state);
return false;
}
return true;
@@ -322,7 +326,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return false;
}
- switch (getSinkState(device)) {
+ int state = getSinkState(device);
+ switch (state) {
case BluetoothA2dp.STATE_DISCONNECTED:
return false;
case BluetoothA2dp.STATE_DISCONNECTING:
@@ -330,11 +335,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
}
// State is CONNECTING or CONNECTED or PLAYING
+ handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
if (!disconnectSinkNative(path)) {
+ // Restore previous state
+ handleSinkStateChange(device, mAudioDevices.get(device), state);
return false;
- } else {
- return true;
}
+ return true;
}
public synchronized boolean suspendSink(BluetoothDevice device) {
@@ -510,6 +517,19 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return result;
}
+ private void onConnectSinkResult(String deviceObjectPath, boolean result) {
+ // If the call was a success, ignore we will update the state
+ // when we a Sink Property Change
+ if (!result) {
+ if (deviceObjectPath != null) {
+ String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ int state = getSinkState(device);
+ handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
+ }
+ }
+ }
+
@Override
protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mAudioDevices.isEmpty()) return;
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 018f7d7..dfb775f 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -69,11 +69,12 @@ import java.util.Random;
public class BluetoothService extends IBluetooth.Stub {
private static final String TAG = "BluetoothService";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private int mNativeData;
private BluetoothEventLoop mEventLoop;
private boolean mIsAirplaneSensitive;
+ private boolean mIsAirplaneToggleable;
private int mBluetoothState;
private boolean mRestart = false; // need to call enable() after disable()
private boolean mIsDiscovering;
@@ -370,7 +371,7 @@ public class BluetoothService extends IBluetooth.Stub {
"Need BLUETOOTH_ADMIN permission");
// Airplane mode can prevent Bluetooth radio from being turned on.
- if (mIsAirplaneSensitive && isAirplaneModeOn()) {
+ if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
return false;
}
if (mBluetoothState != BluetoothAdapter.STATE_OFF) {
@@ -545,7 +546,7 @@ public class BluetoothService extends IBluetooth.Stub {
mEventLoop.onPropertyChanged(propVal);
}
- if (mIsAirplaneSensitive && isAirplaneModeOn()) {
+ if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
disable(false);
}
@@ -1597,10 +1598,17 @@ public class BluetoothService extends IBluetooth.Stub {
};
private void registerForAirplaneMode(IntentFilter filter) {
- String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(),
+ final ContentResolver resolver = mContext.getContentResolver();
+ final String airplaneModeRadios = Settings.System.getString(resolver,
Settings.System.AIRPLANE_MODE_RADIOS);
- mIsAirplaneSensitive = airplaneModeRadios == null
- ? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
+ final String toggleableRadios = Settings.System.getString(resolver,
+ Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+
+ mIsAirplaneSensitive = airplaneModeRadios == null ? true :
+ airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
+ mIsAirplaneToggleable = toggleableRadios == null ? false :
+ toggleableRadios.contains(Settings.System.RADIO_BLUETOOTH);
+
if (mIsAirplaneSensitive) {
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
}
@@ -1661,6 +1669,7 @@ public class BluetoothService extends IBluetooth.Stub {
}
pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive);
+ pw.println("mIsAirplaneToggleable = " + mIsAirplaneToggleable);
pw.println("Local address = " + getAddress());
pw.println("Local name = " + getName());
diff --git a/core/java/android/server/data/BuildData.java b/core/java/android/server/data/BuildData.java
deleted file mode 100644
index 53ffa3f..0000000
--- a/core/java/android/server/data/BuildData.java
+++ /dev/null
@@ -1,89 +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.server.data;
-
-import android.os.Build;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-import static com.android.internal.util.Objects.nonNull;
-
-/**
- * Build data transfer object. Keep in sync. with the server side version.
- */
-public class BuildData {
-
- /** The version of the data returned by write() and understood by the constructor. */
- private static final int VERSION = 0;
-
- private final String fingerprint;
- private final String incrementalVersion;
- private final long time; // in *seconds* since the epoch (not msec!)
-
- public BuildData() {
- this.fingerprint = "android:" + Build.FINGERPRINT;
- this.incrementalVersion = Build.VERSION.INCREMENTAL;
- this.time = Build.TIME / 1000; // msec -> sec
- }
-
- public BuildData(String fingerprint, String incrementalVersion, long time) {
- this.fingerprint = nonNull(fingerprint);
- this.incrementalVersion = incrementalVersion;
- this.time = time;
- }
-
- /*package*/ BuildData(DataInput in) throws IOException {
- int dataVersion = in.readInt();
- if (dataVersion != VERSION) {
- throw new IOException("Expected " + VERSION + ". Got: " + dataVersion);
- }
-
- this.fingerprint = in.readUTF();
- this.incrementalVersion = Long.toString(in.readLong());
- this.time = in.readLong();
- }
-
- /*package*/ void write(DataOutput out) throws IOException {
- out.writeInt(VERSION);
- out.writeUTF(fingerprint);
-
- // TODO: change the format/version to expect a string for this field.
- // Version 0, still used by the server side, expects a long.
- long changelist;
- try {
- changelist = Long.parseLong(incrementalVersion);
- } catch (NumberFormatException ex) {
- changelist = -1;
- }
- out.writeLong(changelist);
- out.writeLong(time);
- }
-
- public String getFingerprint() {
- return fingerprint;
- }
-
- public String getIncrementalVersion() {
- return incrementalVersion;
- }
-
- public long getTime() {
- return time;
- }
-}
diff --git a/core/java/android/server/data/CrashData.java b/core/java/android/server/data/CrashData.java
deleted file mode 100644
index d652bb3..0000000
--- a/core/java/android/server/data/CrashData.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.server.data;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-import static com.android.internal.util.Objects.nonNull;
-
-/**
- * Crash data transfer object. Keep in sync. with the server side version.
- */
-public class CrashData {
-
- final String id;
- final String activity;
- final long time;
- final BuildData buildData;
- final ThrowableData throwableData;
- final byte[] state;
-
- public CrashData(String id, String activity, BuildData buildData,
- ThrowableData throwableData) {
- this.id = nonNull(id);
- this.activity = nonNull(activity);
- this.buildData = nonNull(buildData);
- this.throwableData = nonNull(throwableData);
- this.time = System.currentTimeMillis();
- this.state = null;
- }
-
- public CrashData(String id, String activity, BuildData buildData,
- ThrowableData throwableData, byte[] state) {
- this.id = nonNull(id);
- this.activity = nonNull(activity);
- this.buildData = nonNull(buildData);
- this.throwableData = nonNull(throwableData);
- this.time = System.currentTimeMillis();
- this.state = state;
- }
-
- public CrashData(DataInput in) throws IOException {
- int dataVersion = in.readInt();
- if (dataVersion != 0 && dataVersion != 1) {
- throw new IOException("Expected 0 or 1. Got: " + dataVersion);
- }
-
- this.id = in.readUTF();
- this.activity = in.readUTF();
- this.time = in.readLong();
- this.buildData = new BuildData(in);
- this.throwableData = new ThrowableData(in);
- if (dataVersion == 1) {
- int len = in.readInt();
- if (len == 0) {
- this.state = null;
- } else {
- this.state = new byte[len];
- in.readFully(this.state, 0, len);
- }
- } else {
- this.state = null;
- }
- }
-
- public CrashData(String tag, Throwable throwable) {
- id = "";
- activity = tag;
- buildData = new BuildData();
- throwableData = new ThrowableData(throwable);
- time = System.currentTimeMillis();
- state = null;
- }
-
- public void write(DataOutput out) throws IOException {
- // version
- if (this.state == null) {
- out.writeInt(0);
- } else {
- out.writeInt(1);
- }
-
- out.writeUTF(this.id);
- out.writeUTF(this.activity);
- out.writeLong(this.time);
- buildData.write(out);
- throwableData.write(out);
- if (this.state != null) {
- out.writeInt(this.state.length);
- out.write(this.state, 0, this.state.length);
- }
- }
-
- public BuildData getBuildData() {
- return buildData;
- }
-
- public ThrowableData getThrowableData() {
- return throwableData;
- }
-
- public String getId() {
- return id;
- }
-
- public String getActivity() {
- return activity;
- }
-
- public long getTime() {
- return time;
- }
-
- public byte[] getState() {
- return state;
- }
-
- /**
- * Return a brief description of this CrashData record. The details of the
- * representation are subject to change.
- *
- * @return Returns a String representing the contents of the object.
- */
- @Override
- public String toString() {
- return "[CrashData: id=" + id + " activity=" + activity + " time=" + time +
- " buildData=" + buildData.toString() +
- " throwableData=" + throwableData.toString() + "]";
- }
-}
diff --git a/core/java/android/server/data/StackTraceElementData.java b/core/java/android/server/data/StackTraceElementData.java
deleted file mode 100644
index 07185a0..0000000
--- a/core/java/android/server/data/StackTraceElementData.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.server.data;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-/**
- * Stack trace element data transfer object. Keep in sync. with the server side
- * version.
- */
-public class StackTraceElementData {
-
- final String className;
- final String fileName;
- final String methodName;
- final int lineNumber;
-
- public StackTraceElementData(StackTraceElement element) {
- this.className = element.getClassName();
-
- String fileName = element.getFileName();
- this.fileName = fileName == null ? "[unknown source]" : fileName;
-
- this.methodName = element.getMethodName();
- this.lineNumber = element.getLineNumber();
- }
-
- public StackTraceElementData(DataInput in) throws IOException {
- int dataVersion = in.readInt();
- if (dataVersion != 0) {
- throw new IOException("Expected 0. Got: " + dataVersion);
- }
-
- this.className = in.readUTF();
- this.fileName = in.readUTF();
- this.methodName = in.readUTF();
- this.lineNumber = in.readInt();
- }
-
- void write(DataOutput out) throws IOException {
- out.writeInt(0); // version
-
- out.writeUTF(className);
- out.writeUTF(fileName);
- out.writeUTF(methodName);
- out.writeInt(lineNumber);
- }
-
- public String getClassName() {
- return className;
- }
-
- public String getFileName() {
- return fileName;
- }
-
- public String getMethodName() {
- return methodName;
- }
-
- public int getLineNumber() {
- return lineNumber;
- }
-}
diff --git a/core/java/android/server/data/ThrowableData.java b/core/java/android/server/data/ThrowableData.java
deleted file mode 100644
index e500aca..0000000
--- a/core/java/android/server/data/ThrowableData.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.server.data;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-/**
- * Throwable data transfer object. Keep in sync. with the server side version.
- */
-public class ThrowableData {
-
- final String message;
- final String type;
- final StackTraceElementData[] stackTrace;
- final ThrowableData cause;
-
- public ThrowableData(Throwable throwable) {
- this.type = throwable.getClass().getName();
- String message = throwable.getMessage();
- this.message = message == null ? "" : message;
-
- StackTraceElement[] elements = throwable.getStackTrace();
- this.stackTrace = new StackTraceElementData[elements.length];
- for (int i = 0; i < elements.length; i++) {
- this.stackTrace[i] = new StackTraceElementData(elements[i]);
- }
-
- Throwable cause = throwable.getCause();
- this.cause = cause == null ? null : new ThrowableData(cause);
- }
-
- public ThrowableData(DataInput in) throws IOException {
- int dataVersion = in.readInt();
- if (dataVersion != 0) {
- throw new IOException("Expected 0. Got: " + dataVersion);
- }
-
- this.message = in.readUTF();
- this.type = in.readUTF();
-
- int count = in.readInt();
- this.stackTrace = new StackTraceElementData[count];
- for (int i = 0; i < count; i++) {
- this.stackTrace[i] = new StackTraceElementData(in);
- }
-
- this.cause = in.readBoolean() ? new ThrowableData(in) : null;
- }
-
- public void write(DataOutput out) throws IOException {
- out.writeInt(0); // version
-
- out.writeUTF(message);
- out.writeUTF(type);
-
- out.writeInt(stackTrace.length);
- for (StackTraceElementData elementData : stackTrace) {
- elementData.write(out);
- }
-
- out.writeBoolean(cause != null);
- if (cause != null) {
- cause.write(out);
- }
- }
-
- public String getMessage() {
- return message;
- }
-
- public String getType() {
- return type;
- }
-
- public StackTraceElementData[] getStackTrace() {
- return stackTrace;
- }
-
- public ThrowableData getCause() {
- return cause;
- }
-
-
- public String toString() {
- return toString(null);
- }
-
- public String toString(String prefix) {
- StringBuilder builder = new StringBuilder();
- append(prefix, builder, this);
- return builder.toString();
- }
-
- private static void append(String prefix, StringBuilder builder,
- ThrowableData throwableData) {
- if (prefix != null) builder.append(prefix);
- builder.append(throwableData.getType())
- .append(": ")
- .append(throwableData.getMessage())
- .append('\n');
- for (StackTraceElementData element : throwableData.getStackTrace()) {
- if (prefix != null ) builder.append(prefix);
- builder.append(" at ")
- .append(element.getClassName())
- .append('.')
- .append(element.getMethodName())
- .append("(")
- .append(element.getFileName())
- .append(':')
- .append(element.getLineNumber())
- .append(")\n");
-
- }
-
- ThrowableData cause = throwableData.getCause();
- if (cause != null) {
- if (prefix != null ) builder.append(prefix);
- builder.append("Caused by: ");
- append(prefix, builder, cause);
- }
- }
-}
diff --git a/core/java/android/server/data/package.html b/core/java/android/server/data/package.html
deleted file mode 100755
index 1c9bf9d..0000000
--- a/core/java/android/server/data/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<body>
- {@hide}
-</body>
-</html>
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 78ea2e3..f9a0b93 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -21,6 +21,7 @@ import android.app.IActivityWatcher;
import android.app.ISearchManager;
import android.app.ISearchManagerCallback;
import android.app.SearchManager;
+import android.app.SearchableInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -149,8 +150,9 @@ public class SearchManagerService extends ISearchManager.Stub {
* Informs all listeners that the list of searchables has been updated.
*/
void broadcastSearchablesChanged() {
- mContext.sendBroadcast(
- new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ mContext.sendBroadcast(intent);
}
//
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
index c615957..cbb63a5 100644
--- a/core/java/android/server/search/Searchables.java
+++ b/core/java/android/server/search/Searchables.java
@@ -19,6 +19,7 @@ package android.server.search;
import com.android.internal.app.ResolverActivity;
import android.app.SearchManager;
+import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 45719e4..fe3b149 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -434,9 +434,9 @@ public abstract class WallpaperService extends Service {
}
int myWidth = mSurfaceHolder.getRequestedWidth();
- if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.FILL_PARENT;
+ if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT;
int myHeight = mSurfaceHolder.getRequestedHeight();
- if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.FILL_PARENT;
+ if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT;
final boolean creating = !mCreated;
final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();
diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl
index 1812188..2ed660a 100755
--- a/core/java/android/speech/tts/ITts.aidl
+++ b/core/java/android/speech/tts/ITts.aidl
@@ -59,5 +59,7 @@ interface ITts {
int unregisterCallback(in String callingApp, ITtsCallback cb);
- int playSilence(in String callingApp, in long duration, in int queueMode, in String[] params);
+ int playSilence(in String callingApp, in long duration, in int queueMode, in String[] params);
+
+ int setEngineByPackageName(in String enginePackageName);
}
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 3f369dd..bbbeb3f 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -129,8 +129,8 @@ public class TextToSpeech {
* {@link TextToSpeech#synthesizeToFile(String, HashMap, String)} with the
* {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID} key.
* @param utteranceId the identifier of the utterance.
- */
- public void onUtteranceCompleted(String utteranceId);
+ */
+ public void onUtteranceCompleted(String utteranceId);
}
@@ -286,6 +286,10 @@ public class TextToSpeech {
*/
public static final String KEY_PARAM_VARIANT = "variant";
/**
+ * {@hide}
+ */
+ public static final String KEY_PARAM_ENGINE = "engine";
+ /**
* Parameter key to specify the audio stream type to be used when speaking text
* or playing back a file.
* @see TextToSpeech#speak(String, int, HashMap)
@@ -327,10 +331,16 @@ public class TextToSpeech {
* {@hide}
*/
protected static final int PARAM_POSITION_UTTERANCE_ID = 10;
+
+ /**
+ * {@hide}
+ */
+ protected static final int PARAM_POSITION_ENGINE = 12;
+
/**
* {@hide}
*/
- protected static final int NB_CACHED_PARAMS = 6;
+ protected static final int NB_CACHED_PARAMS = 7;
}
/**
@@ -373,6 +383,7 @@ public class TextToSpeech {
mCachedParams[Engine.PARAM_POSITION_VARIANT] = Engine.KEY_PARAM_VARIANT;
mCachedParams[Engine.PARAM_POSITION_STREAM] = Engine.KEY_PARAM_STREAM;
mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID] = Engine.KEY_PARAM_UTTERANCE_ID;
+ mCachedParams[Engine.PARAM_POSITION_ENGINE] = Engine.KEY_PARAM_ENGINE;
mCachedParams[Engine.PARAM_POSITION_RATE + 1] =
String.valueOf(Engine.DEFAULT_RATE);
@@ -381,10 +392,10 @@ public class TextToSpeech {
mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = defaultLoc.getISO3Language();
mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = defaultLoc.getISO3Country();
mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = defaultLoc.getVariant();
-
mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
String.valueOf(Engine.DEFAULT_STREAM);
mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = "";
+ mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = Engine.DEFAULT_SYNTH;
initTts();
}
@@ -684,6 +695,10 @@ public class TextToSpeech {
if (extra != null) {
mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = extra;
}
+ extra = params.get(Engine.KEY_PARAM_ENGINE);
+ if (extra != null) {
+ mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = extra;
+ }
}
result = mITts.speak(mPackageName, text, queueMode, mCachedParams);
} catch (RemoteException e) {
@@ -819,7 +834,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -894,7 +909,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -943,7 +958,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -990,7 +1005,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -1046,7 +1061,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -1064,7 +1079,7 @@ public class TextToSpeech {
return null;
}
try {
- String[] locStrings = mITts.getLanguage();
+ String[] locStrings = mITts.getLanguage();
if ((locStrings != null) && (locStrings.length == 3)) {
return new Locale(locStrings[0], locStrings[1], locStrings[2]);
} else {
@@ -1131,7 +1146,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -1166,6 +1181,10 @@ public class TextToSpeech {
if (extra != null) {
mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = extra;
}
+ extra = params.get(Engine.KEY_PARAM_ENGINE);
+ if (extra != null) {
+ mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = extra;
+ }
}
if (mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename)){
result = SUCCESS;
@@ -1214,19 +1233,19 @@ public class TextToSpeech {
*
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
- public int setOnUtteranceCompletedListener(
- final OnUtteranceCompletedListener listener) {
+ public int setOnUtteranceCompletedListener(
+ final OnUtteranceCompletedListener listener) {
synchronized (mStartLock) {
int result = ERROR;
if (!mStarted) {
return result;
}
mITtscallback = new ITtsCallback.Stub() {
- public void utteranceCompleted(String utteranceId) throws RemoteException {
- if (listener != null) {
- listener.onUtteranceCompleted(utteranceId);
- }
- }
+ public void utteranceCompleted(String utteranceId) throws RemoteException {
+ if (listener != null) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
+ }
};
try {
result = mITts.registerCallback(mPackageName, mITtscallback);
@@ -1251,7 +1270,50 @@ public class TextToSpeech {
} finally {
return result;
}
- }
+ }
+ }
+
+ /**
+ * Sets the speech synthesis engine to be used by its packagename.
+ *
+ * @param enginePackageName
+ * The packagename for the synthesis engine (ie, "com.svox.pico")
+ *
+ * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ */
+ public int setEngineByPackageName(String enginePackageName) {
+ synchronized (mStartLock) {
+ int result = TextToSpeech.ERROR;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ result = mITts.setEngineByPackageName(enginePackageName);
+ if (result == TextToSpeech.SUCCESS){
+ mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = enginePackageName;
+ }
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ Log.e("TextToSpeech.java - setEngineByPackageName", "RemoteException");
+ e.printStackTrace();
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ Log.e("TextToSpeech.java - setEngineByPackageName", "NullPointerException");
+ e.printStackTrace();
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ Log.e("TextToSpeech.java - setEngineByPackageName", "IllegalStateException");
+ e.printStackTrace();
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
}
}
diff --git a/core/java/android/text/AutoText.java b/core/java/android/text/AutoText.java
index 2fc906a..862305b 100644
--- a/core/java/android/text/AutoText.java
+++ b/core/java/android/text/AutoText.java
@@ -18,7 +18,7 @@ package android.text;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import android.view.View;
import org.xmlpull.v1.XmlPullParser;
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 380e5fd..33ecc01 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -46,7 +46,7 @@ import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import java.io.IOException;
import java.io.StringReader;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index a92800d..afc6864 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -294,7 +294,12 @@ public abstract class Layout {
lbaseline, lbottom, buf,
start, end, par, this);
- left += margin.getLeadingMargin(par);
+ boolean useMargin = par;
+ if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
+ useMargin = count > i;
+ }
+ left += margin.getLeadingMargin(useMargin);
}
}
}
@@ -1293,7 +1298,13 @@ public abstract class Layout {
LeadingMarginSpan.class);
for (int i = 0; i < spans.length; i++) {
- left += spans[i].getLeadingMargin(par);
+ boolean margin = par;
+ LeadingMarginSpan span = spans[i];
+ if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
+ margin = count >= line;
+ }
+ left += span.getLeadingMargin(margin);
}
}
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f0a5ffd..fbf1261 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -161,6 +161,7 @@ extends Layout
else
end++;
+ int firstWidthLineCount = 1;
int firstwidth = outerwidth;
int restwidth = outerwidth;
@@ -171,8 +172,12 @@ extends Layout
sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
firstwidth -= sp[i].getLeadingMargin(true);
restwidth -= sp[i].getLeadingMargin(false);
+ if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
+ }
}
chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
@@ -750,7 +755,9 @@ extends Layout
fitascent = fitdescent = fittop = fitbottom = 0;
okascent = okdescent = oktop = okbottom = 0;
- width = restwidth;
+ if (--firstWidthLineCount <= 0) {
+ width = restwidth;
+ }
}
}
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 53096dd..afb22ac 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1501,6 +1501,28 @@ public class TextUtils {
}
/**
+ * @hide
+ */
+ public static boolean isPrintableAscii(final char c) {
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x7E; // included
+ return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n';
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isPrintableAsciiOnly(final CharSequence str) {
+ final int len = str.length();
+ for (int i = 0; i < len; i++) {
+ if (!isPrintableAscii(str.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Capitalization mode for {@link #getCapsMode}: capitalize all
* characters. This value is explicitly defined to be the same as
* {@link InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}.
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index ab33cb3..7b307f8 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -54,7 +54,7 @@ implements MovementMethod
Selection.setSelection(buffer, 0);
return true;
} else {
- return Selection.moveUp(buffer, layout);
+ return Selection.moveUp(buffer, layout);
}
}
}
@@ -80,7 +80,7 @@ implements MovementMethod
Selection.setSelection(buffer, buffer.length());
return true;
} else {
- return Selection.moveDown(buffer, layout);
+ return Selection.moveDown(buffer, layout);
}
}
}
@@ -133,6 +133,35 @@ implements MovementMethod
}
}
+ private int getOffset(int x, int y, TextView widget){
+ // Converts the absolute X,Y coordinates to the character offset for the
+ // character whose position is closest to the specified
+ // horizontal position.
+ x -= widget.getTotalPaddingLeft();
+ y -= widget.getTotalPaddingTop();
+
+ // Clamp the position to inside of the view.
+ if (x < 0) {
+ x = 0;
+ } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
+ x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
+ }
+ if (y < 0) {
+ y = 0;
+ } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
+ y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
+ }
+
+ x += widget.getScrollX();
+ y += widget.getScrollY();
+
+ Layout layout = widget.getLayout();
+ int line = layout.getLineForVertical(y);
+
+ int offset = layout.getOffsetForHorizontal(line, x);
+ return offset;
+ }
+
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
if (executeDown(widget, buffer, keyCode)) {
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -196,12 +225,12 @@ implements MovementMethod
}
return false;
}
-
+
public boolean onTrackballEvent(TextView widget, Spannable text,
MotionEvent event) {
return false;
}
-
+
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int initialScrollX = -1, initialScrollY = -1;
@@ -209,11 +238,101 @@ implements MovementMethod
initialScrollX = Touch.getInitialScrollX(widget, buffer);
initialScrollY = Touch.getInitialScrollY(widget, buffer);
}
-
+
boolean handled = Touch.onTouchEvent(widget, buffer, event);
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int offset = getOffset(x, y, widget);
+
+ if (cap) {
+
+ buffer.setSpan(LAST_TAP_DOWN, offset, offset,
+ Spannable.SPAN_POINT_POINT);
+
+ // Disallow intercepting of the touch events, so that
+ // users can scroll and select at the same time.
+ // without this, users would get booted out of select
+ // mode once the view detected it needed to scroll.
+ widget.getParent().requestDisallowInterceptTouchEvent(true);
+ } else {
+ OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
+ OnePointFiveTapState.class);
+
+ if (tap.length > 0) {
+ if (event.getEventTime() - tap[0].mWhen <=
+ ViewConfiguration.getDoubleTapTimeout() &&
+ sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) {
+
+ tap[0].active = true;
+ MetaKeyKeyListener.startSelecting(widget, buffer);
+ widget.getParent().requestDisallowInterceptTouchEvent(true);
+ buffer.setSpan(LAST_TAP_DOWN, offset, offset,
+ Spannable.SPAN_POINT_POINT);
+ }
+
+ tap[0].mWhen = event.getEventTime();
+ } else {
+ OnePointFiveTapState newtap = new OnePointFiveTapState();
+ newtap.mWhen = event.getEventTime();
+ newtap.active = false;
+ buffer.setSpan(newtap, 0, buffer.length(),
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_MOVE ) {
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
+
+ if (cap & handled) {
+ // Before selecting, make sure we've moved out of the "slop".
+ // handled will be true, if we're in select mode AND we're
+ // OUT of the slop
+
+ // Turn long press off while we're selecting. User needs to
+ // re-tap on the selection to enable longpress
+ widget.cancelLongPress();
+
+ // Update selection as we're moving the selection area.
+
+ // Get the current touch position
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int offset = getOffset(x, y, widget);
+
+ // Get the last down touch position (the position at which the
+ // user started the selection)
+ int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN);
+
+ // Compute the selection boundaries
+ int spanstart;
+ int spanend;
+ if (offset >= lastDownOffset) {
+ // Expand from word start of the original tap to new word
+ // end, since we are selecting "forwards"
+ spanstart = findWordStart(buffer, lastDownOffset);
+ spanend = findWordEnd(buffer, offset);
+ } else {
+ // Expand to from new word start to word end of the original
+ // tap since we are selecting "backwards".
+ // The spanend will always need to be associated with the touch
+ // up position, so that refining the selection with the
+ // trackball will work as expected.
+ spanstart = findWordEnd(buffer, lastDownOffset);
+ spanend = findWordStart(buffer, offset);
+ }
+ Selection.setSelection(buffer, spanstart, spanend);
+ return true;
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
// If we have scrolled, then the up shouldn't move the cursor,
// but we do need to make sure the cursor is still visible at
// the current scroll offset to avoid the scroll jumping later
@@ -223,35 +342,26 @@ implements MovementMethod
widget.moveCursorToVisibleOffset();
return true;
}
-
+
int x = (int) event.getX();
int y = (int) event.getY();
+ int off = getOffset(x, y, widget);
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
+ // XXX should do the same adjust for x as we do for the line.
- // Clamp the position to inside of the view.
- if (x < 0) {
- x = 0;
- } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
- x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
- }
- if (y < 0) {
- y = 0;
- } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
- y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
+ OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(),
+ OnePointFiveTapState.class);
+ if (onepointfivetap.length > 0 && onepointfivetap[0].active &&
+ Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) {
+ // If we've set select mode, because there was a onepointfivetap,
+ // but there was no ensuing swipe gesture, undo the select mode
+ // and remove reference to the last onepointfivetap.
+ MetaKeyKeyListener.stopSelecting(widget, buffer);
+ for (int i=0; i < onepointfivetap.length; i++) {
+ buffer.removeSpan(onepointfivetap[i]);
+ }
+ buffer.removeSpan(LAST_TAP_DOWN);
}
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
-
- int off = layout.getOffsetForHorizontal(line, x);
-
- // XXX should do the same adjust for x as we do for the line.
-
boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
KeyEvent.META_SHIFT_ON) == 1) ||
(MetaKeyKeyListener.getMetaState(buffer,
@@ -263,10 +373,10 @@ implements MovementMethod
if (tap.length > 0) {
if (event.getEventTime() - tap[0].mWhen <=
- ViewConfiguration.getDoubleTapTimeout()) {
- if (sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
- doubletap = true;
- }
+ ViewConfiguration.getDoubleTapTimeout() &&
+ sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
+
+ doubletap = true;
}
tap[0].mWhen = event.getEventTime();
@@ -278,7 +388,12 @@ implements MovementMethod
}
if (cap) {
- Selection.extendSelection(buffer, off);
+ buffer.removeSpan(LAST_TAP_DOWN);
+ if (onepointfivetap.length > 0 && onepointfivetap[0].active) {
+ // If we selecting something with the onepointfivetap-and
+ // swipe gesture, stop it on finger up.
+ MetaKeyKeyListener.stopSelecting(widget, buffer);
+ }
} else if (doubletap) {
Selection.setSelection(buffer,
findWordStart(buffer, off),
@@ -301,6 +416,19 @@ implements MovementMethod
long mWhen;
}
+ /* We check for a onepointfive tap. This is similar to
+ * doubletap gesture (where a finger goes down, up, down, up, in a short
+ * time period), except in the onepointfive tap, a users finger only needs
+ * to go down, up, down in a short time period. We detect this type of tap
+ * to implement the onepointfivetap-and-swipe selection gesture.
+ * This gesture allows users to select a segment of text without going
+ * through the "select text" option in the context menu.
+ */
+ private static class OnePointFiveTapState implements NoCopySpan {
+ long mWhen;
+ boolean active;
+ }
+
private static boolean sameWord(CharSequence text, int one, int two) {
int start = findWordStart(text, one);
int end = findWordEnd(text, one);
@@ -395,5 +523,7 @@ implements MovementMethod
return sInstance;
}
+
+ private static final Object LAST_TAP_DOWN = new Object();
private static ArrowKeyMovementMethod sInstance;
}
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index aa8d979..42ad10e 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -24,6 +24,7 @@ import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
+import android.view.KeyEvent;
public class Touch {
private Touch() { }
@@ -139,10 +140,21 @@ public class Touch {
if (ds[0].mFarEnough) {
ds[0].mUsed = true;
-
- float dx = ds[0].mX - event.getX();
- float dy = ds[0].mY - event.getY();
-
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
+ float dx;
+ float dy;
+ if (cap) {
+ // if we're selecting, we want the scroll to go in
+ // the direction of the drag
+ dx = event.getX() - ds[0].mX;
+ dy = event.getY() - ds[0].mY;
+ } else {
+ dx = ds[0].mX - event.getX();
+ dy = ds[0].mY - event.getY();
+ }
ds[0].mX = event.getX();
ds[0].mY = event.getY();
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index 8e212e3..cb55329 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -33,6 +33,11 @@ extends ParagraphStyle
CharSequence text, int start, int end,
boolean first, Layout layout);
+
+ public interface LeadingMarginSpan2 extends LeadingMarginSpan, WrapTogetherSpan {
+ public int getLeadingMarginLineCount();
+ };
+
public static class Standard implements LeadingMarginSpan, ParcelableSpan {
private final int mFirst, mRest;
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index ce25c47..7f87365 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -25,6 +25,8 @@ import android.text.Spanned;
import android.webkit.WebView;
import android.widget.TextView;
+import com.android.common.Patterns;
+
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
@@ -133,7 +135,7 @@ public class Linkify {
*/
public static final TransformFilter sPhoneNumberTransformFilter = new TransformFilter() {
public final String transformUrl(final Matcher match, String url) {
- return Regex.digitsAndPlusOnly(match);
+ return Patterns.digitsAndPlusOnly(match);
}
};
@@ -207,19 +209,19 @@ public class Linkify {
ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
if ((mask & WEB_URLS) != 0) {
- gatherLinks(links, text, Regex.WEB_URL_PATTERN,
+ gatherLinks(links, text, Patterns.WEB_URL,
new String[] { "http://", "https://", "rtsp://" },
sUrlMatchFilter, null);
}
if ((mask & EMAIL_ADDRESSES) != 0) {
- gatherLinks(links, text, Regex.EMAIL_ADDRESS_PATTERN,
+ gatherLinks(links, text, Patterns.EMAIL_ADDRESS,
new String[] { "mailto:" },
null, null);
}
if ((mask & PHONE_NUMBERS) != 0) {
- gatherLinks(links, text, Regex.PHONE_PATTERN,
+ gatherLinks(links, text, Patterns.PHONE,
new String[] { "tel:" },
sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
}
diff --git a/core/java/android/text/util/Regex.java b/core/java/android/text/util/Regex.java
deleted file mode 100644
index a6844a4..0000000
--- a/core/java/android/text/util/Regex.java
+++ /dev/null
@@ -1,204 +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.text.util;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @hide
- */
-public class Regex {
- /**
- * Regular expression pattern to match all IANA top-level domains.
- * List accurate as of 2007/06/15. List taken from:
- * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
- * This pattern is auto-generated by //device/tools/make-iana-tld-pattern.py
- */
- public static final Pattern TOP_LEVEL_DOMAIN_PATTERN
- = Pattern.compile(
- "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
- + "|(biz|b[abdefghijmnorstvwyz])"
- + "|(cat|com|coop|c[acdfghiklmnoruvxyz])"
- + "|d[ejkmoz]"
- + "|(edu|e[cegrstu])"
- + "|f[ijkmor]"
- + "|(gov|g[abdefghilmnpqrstuwy])"
- + "|h[kmnrtu]"
- + "|(info|int|i[delmnoqrst])"
- + "|(jobs|j[emop])"
- + "|k[eghimnrwyz]"
- + "|l[abcikrstuvy]"
- + "|(mil|mobi|museum|m[acdghklmnopqrstuvwxyz])"
- + "|(name|net|n[acefgilopruz])"
- + "|(org|om)"
- + "|(pro|p[aefghklmnrstwy])"
- + "|qa"
- + "|r[eouw]"
- + "|s[abcdeghijklmnortuvyz]"
- + "|(tel|travel|t[cdfghjklmnoprtvwz])"
- + "|u[agkmsyz]"
- + "|v[aceginu]"
- + "|w[fs]"
- + "|y[etu]"
- + "|z[amw])");
-
- /**
- * Regular expression pattern to match RFC 1738 URLs
- * List accurate as of 2007/06/15. List taken from:
- * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
- * This pattern is auto-generated by //device/tools/make-iana-tld-pattern.py
- */
- public static final Pattern WEB_URL_PATTERN
- = Pattern.compile(
- "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
- + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
- + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
- + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}\\.)+" // named host
- + "(?:" // plus top level domain
- + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
- + "|(?:biz|b[abdefghijmnorstvwyz])"
- + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
- + "|d[ejkmoz]"
- + "|(?:edu|e[cegrstu])"
- + "|f[ijkmor]"
- + "|(?:gov|g[abdefghilmnpqrstuwy])"
- + "|h[kmnrtu]"
- + "|(?:info|int|i[delmnoqrst])"
- + "|(?:jobs|j[emop])"
- + "|k[eghimnrwyz]"
- + "|l[abcikrstuvy]"
- + "|(?:mil|mobi|museum|m[acdghklmnopqrstuvwxyz])"
- + "|(?:name|net|n[acefgilopruz])"
- + "|(?:org|om)"
- + "|(?:pro|p[aefghklmnrstwy])"
- + "|qa"
- + "|r[eouw]"
- + "|s[abcdeghijklmnortuvyz]"
- + "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
- + "|u[agkmsyz]"
- + "|v[aceginu]"
- + "|w[fs]"
- + "|y[etu]"
- + "|z[amw]))"
- + "|(?:(?:25[0-5]|2[0-4]" // or ip address
- + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
- + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
- + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
- + "|[1-9][0-9]|[0-9])))"
- + "(?:\\:\\d{1,5})?)" // plus option port number
- + "(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
- + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
- + "(?:\\b|$)"); // and finally, a word boundary or end of
- // input. This is to stop foo.sure from
- // matching as foo.su
-
- public static final Pattern IP_ADDRESS_PATTERN
- = Pattern.compile(
- "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
- + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
- + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
- + "|[1-9][0-9]|[0-9]))");
-
- public static final Pattern DOMAIN_NAME_PATTERN
- = Pattern.compile(
- "(((([a-zA-Z0-9][a-zA-Z0-9\\-]*)*[a-zA-Z0-9]\\.)+"
- + TOP_LEVEL_DOMAIN_PATTERN + ")|"
- + IP_ADDRESS_PATTERN + ")");
-
- public static final Pattern EMAIL_ADDRESS_PATTERN
- = Pattern.compile(
- "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" +
- "\\@" +
- "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
- "(" +
- "\\." +
- "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
- ")+"
- );
-
- /**
- * This pattern is intended for searching for things that look like they
- * might be phone numbers in arbitrary text, not for validating whether
- * something is in fact a phone number. It will miss many things that
- * are legitimate phone numbers.
- *
- * <p> The pattern matches the following:
- * <ul>
- * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
- * may follow.
- * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
- * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
- * </ul>
- */
- public static final Pattern PHONE_PATTERN
- = Pattern.compile( // sdd = space, dot, or dash
- "(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
- + "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>*
- + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
-
- /**
- * Convenience method to take all of the non-null matching groups in a
- * regex Matcher and return them as a concatenated string.
- *
- * @param matcher The Matcher object from which grouped text will
- * be extracted
- *
- * @return A String comprising all of the non-null matched
- * groups concatenated together
- */
- public static final String concatGroups(Matcher matcher) {
- StringBuilder b = new StringBuilder();
- final int numGroups = matcher.groupCount();
-
- for (int i = 1; i <= numGroups; i++) {
- String s = matcher.group(i);
-
- System.err.println("Group(" + i + ") : " + s);
-
- if (s != null) {
- b.append(s);
- }
- }
-
- return b.toString();
- }
-
- /**
- * Convenience method to return only the digits and plus signs
- * in the matching string.
- *
- * @param matcher The Matcher object from which digits and plus will
- * be extracted
- *
- * @return A String comprising all of the digits and plus in
- * the match
- */
- public static final String digitsAndPlusOnly(Matcher matcher) {
- StringBuilder buffer = new StringBuilder();
- String matchingRegion = matcher.group();
-
- for (int i = 0, size = matchingRegion.length(); i < size; i++) {
- char character = matchingRegion.charAt(i);
-
- if (character == '+' || Character.isDigit(character)) {
- buffer.append(character);
- }
- }
- return buffer.toString();
- }
-}
diff --git a/core/java/android/text/util/Rfc822InputFilter.java b/core/java/android/text/util/Rfc822InputFilter.java
deleted file mode 100644
index 8c8b7fc..0000000
--- a/core/java/android/text/util/Rfc822InputFilter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package android.text.util;
-
-import android.text.InputFilter;
-import android.text.Spanned;
-import android.text.SpannableStringBuilder;
-
-/**
- * Implements special address cleanup rules:
- * The first space key entry following an "@" symbol that is followed by any combination
- * of letters and symbols, including one+ dots and zero commas, should insert an extra
- * comma (followed by the space).
- *
- * @hide
- */
-public class Rfc822InputFilter implements InputFilter {
-
- public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
- int dstart, int dend) {
-
- // quick check - did they enter a single space?
- if (end-start != 1 || source.charAt(start) != ' ') {
- return null;
- }
-
- // determine if the characters before the new space fit the pattern
- // follow backwards and see if we find a comma, dot, or @
- int scanBack = dstart;
- boolean dotFound = false;
- while (scanBack > 0) {
- char c = dest.charAt(--scanBack);
- switch (c) {
- case '.':
- dotFound = true; // one or more dots are req'd
- break;
- case ',':
- return null;
- case '@':
- if (!dotFound) {
- return null;
- }
- // we have found a comma-insert case. now just do it
- // in the least expensive way we can.
- if (source instanceof Spanned) {
- SpannableStringBuilder sb = new SpannableStringBuilder(",");
- sb.append(source);
- return sb;
- } else {
- return ", ";
- }
- default:
- // just keep going
- }
- }
-
- // no termination cases were found, so don't edit the input
- return null;
- }
-}
diff --git a/core/java/android/text/util/Rfc822Validator.java b/core/java/android/text/util/Rfc822Validator.java
deleted file mode 100644
index 6a6bf69..0000000
--- a/core/java/android/text/util/Rfc822Validator.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.
- */
-
-package android.text.util;
-
-import android.text.TextUtils;
-import android.widget.AutoCompleteTextView;
-
-import java.util.regex.Pattern;
-
-/**
- * This class works as a Validator for AutoCompleteTextView for
- * email addresses. If a token does not appear to be a valid address,
- * it is trimmed of characters that cannot legitimately appear in one
- * and has the specified domain name added. It is meant for use with
- * {@link Rfc822Token} and {@link Rfc822Tokenizer}.
- *
- * @deprecated In the future make sure we don't quietly alter the user's
- * text in ways they did not intend. Meanwhile, hide this
- * class from the public API because it does not even have
- * a full understanding of the syntax it claims to correct.
- * @hide
- */
-public class Rfc822Validator implements AutoCompleteTextView.Validator {
- /*
- * Regex.EMAIL_ADDRESS_PATTERN hardcodes the TLD that we accept, but we
- * want to make sure we will keep accepting email addresses with TLD's
- * that don't exist at the time of this writing, so this regexp relaxes
- * that constraint by accepting any kind of top level domain, not just
- * ".com", ".fr", etc...
- */
- private static final Pattern EMAIL_ADDRESS_PATTERN =
- Pattern.compile("[^\\s@]+@[^\\s@]+\\.[a-zA-z][a-zA-Z][a-zA-Z]*");
-
- private String mDomain;
-
- /**
- * Constructs a new validator that uses the specified domain name as
- * the default when none is specified.
- */
- public Rfc822Validator(String domain) {
- mDomain = domain;
- }
-
- /**
- * {@inheritDoc}
- */
- public boolean isValid(CharSequence text) {
- Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text);
-
- return tokens.length == 1 &&
- EMAIL_ADDRESS_PATTERN.
- matcher(tokens[0].getAddress()).matches();
- }
-
- /**
- * @return a string in which all the characters that are illegal for the username
- * or the domain name part of the email address have been removed.
- */
- private String removeIllegalCharacters(String s) {
- StringBuilder result = new StringBuilder();
- int length = s.length();
- for (int i = 0; i < length; i++) {
- char c = s.charAt(i);
-
- /*
- * An RFC822 atom can contain any ASCII printing character
- * except for periods and any of the following punctuation.
- * A local-part can contain multiple atoms, concatenated by
- * periods, so do allow periods here.
- */
-
- if (c <= ' ' || c > '~') {
- continue;
- }
-
- if (c == '(' || c == ')' || c == '<' || c == '>' ||
- c == '@' || c == ',' || c == ';' || c == ':' ||
- c == '\\' || c == '"' || c == '[' || c == ']') {
- continue;
- }
-
- result.append(c);
- }
- return result.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- public CharSequence fixText(CharSequence cs) {
- // Return an empty string if the email address only contains spaces, \n or \t
- if (TextUtils.getTrimmedLength(cs) == 0) return "";
-
- Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs);
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < tokens.length; i++) {
- String text = tokens[i].getAddress();
- int index = text.indexOf('@');
- if (index < 0) {
- // If there is no @, just append the domain of the account
- tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain);
- } else {
- // Otherwise, remove the illegal characters on both sides of the '@'
- String fix = removeIllegalCharacters(text.substring(0, index));
- String domain = removeIllegalCharacters(text.substring(index + 1));
- tokens[i].setAddress(fix + "@" + (domain.length() != 0 ? domain : mDomain));
- }
-
- sb.append(tokens[i].toString());
- if (i + 1 < tokens.length) {
- sb.append(", ");
- }
- }
-
- return sb;
- }
-}
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 81dd96e..b596d32 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -16,134 +16,41 @@
package android.util;
-import com.google.android.collect.Lists;
-
+import java.io.BufferedReader;
+import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
- * {@hide}
- * Dynamically defined (in terms of event types), space efficient (i.e. "tight") event logging
- * to help instrument code for large scale stability and performance monitoring.
- *
- * Note that this class contains all static methods. This is done for efficiency reasons.
- *
- * Events for the event log are self-describing binary data structures. They start with a 20 byte
- * header (generated automatically) which contains all of the following in order:
- *
- * <ul>
- * <li> Payload length: 2 bytes - length of the non-header portion </li>
- * <li> Padding: 2 bytes - no meaning at this time </li>
- * <li> Timestamp:
- * <ul>
- * <li> Seconds: 4 bytes - seconds since Epoch </li>
- * <li> Nanoseconds: 4 bytes - plus extra nanoseconds </li>
- * </ul></li>
- * <li> Process ID: 4 bytes - matching {@link android.os.Process#myPid} </li>
- * <li> Thread ID: 4 bytes - matching {@link android.os.Process#myTid} </li>
- * </li>
- * </ul>
+ * Access to the system diagnostic event record. System diagnostic events are
+ * used to record certain system-level events (such as garbage collection,
+ * activity manager state, system watchdogs, and other low level activity),
+ * which may be automatically collected and analyzed during system development.
*
- * The above is followed by a payload, comprised of the following:
- * <ul>
- * <li> Tag: 4 bytes - unique integer used to identify a particular event. This number is also
- * used as a key to map to a string that can be displayed by log reading tools.
- * </li>
- * <li> Type: 1 byte - can be either {@link #INT}, {@link #LONG}, {@link #STRING},
- * or {@link #LIST}. </li>
- * <li> Event log value: the size and format of which is one of:
- * <ul>
- * <li> INT: 4 bytes </li>
- * <li> LONG: 8 bytes </li>
- * <li> STRING:
- * <ul>
- * <li> Size of STRING: 4 bytes </li>
- * <li> The string: n bytes as specified in the size fields above. </li>
- * </ul></li>
- * <li> {@link List LIST}:
- * <ul>
- * <li> Num items: 1 byte </li>
- * <li> N value payloads, where N is the number of items specified above. </li>
- * </ul></li>
- * </ul>
- * </li>
- * <li> '\n': 1 byte - an automatically generated newline, used to help detect and recover from log
- * corruption and enable standard unix tools like grep, tail and wc to operate
- * on event logs. </li>
- * </ul>
+ * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
+ * These diagnostic events are for system integrators, not application authors.
*
- * Note that all output is done in the endian-ness of the device (as determined
- * by {@link ByteOrder#nativeOrder()}).
+ * <p>Events use integer tag codes corresponding to /system/etc/event-log-tags.
+ * They carry a payload of one or more int, long, or String values. The
+ * event-log-tags file defines the payload contents for each type code.
*/
-
public class EventLog {
+ private static final String TAG = "EventLog";
- // Value types
- public static final byte INT = 0;
- public static final byte LONG = 1;
- public static final byte STRING = 2;
- public static final byte LIST = 3;
+ private static final String TAGS_FILE = "/system/etc/event-log-tags";
+ private static final String COMMENT_PATTERN = "^\\s*(#.*)?$";
+ private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$";
+ private static HashMap<String, Integer> sTagCodes = null;
+ private static HashMap<Integer, String> sTagNames = null;
- /**
- * An immutable tuple used to log a heterogeneous set of loggable items.
- * The items can be Integer, Long, String, or {@link List}.
- * The maximum number of items is 127
- */
- public static final class List {
- private Object[] mItems;
-
- /**
- * Get a particular tuple item
- * @param pos The position of the item in the tuple
- */
- public final Object getItem(int pos) {
- return mItems[pos];
- }
-
- /**
- * Get the number of items in the tuple.
- */
- public final byte getNumItems() {
- return (byte) mItems.length;
- }
-
- /**
- * Create a new tuple.
- * @param items The items to create the tuple with, as varargs.
- * @throws IllegalArgumentException if the arguments are too few (0),
- * too many, or aren't loggable types.
- */
- public List(Object... items) throws IllegalArgumentException {
- if (items.length > Byte.MAX_VALUE) {
- throw new IllegalArgumentException(
- "A List must have fewer than "
- + Byte.MAX_VALUE + " items in it.");
- }
- for (int i = 0; i < items.length; i++) {
- final Object item = items[i];
- if (item == null) {
- // Would be nice to be able to write null strings...
- items[i] = "";
- } else if (!(item instanceof List ||
- item instanceof String ||
- item instanceof Integer ||
- item instanceof Long)) {
- throw new IllegalArgumentException(
- "Attempt to create a List with illegal item type.");
- }
- }
- this.mItems = items;
- }
- }
-
- /**
- * A previously logged event read from the logs.
- */
+ /** A previously logged event read from the logs. */
public static final class Event {
private final ByteBuffer mBuffer;
@@ -158,77 +65,84 @@ public class EventLog {
private static final int TAG_OFFSET = 20;
private static final int DATA_START = 24;
+ // Value types
+ private static final byte INT_TYPE = 0;
+ private static final byte LONG_TYPE = 1;
+ private static final byte STRING_TYPE = 2;
+ private static final byte LIST_TYPE = 3;
+
/** @param data containing event, read from the system */
- public Event(byte[] data) {
+ /*package*/ Event(byte[] data) {
mBuffer = ByteBuffer.wrap(data);
mBuffer.order(ByteOrder.nativeOrder());
}
+ /** @return the process ID which wrote the log entry */
public int getProcessId() {
return mBuffer.getInt(PROCESS_OFFSET);
}
+ /** @return the thread ID which wrote the log entry */
public int getThreadId() {
return mBuffer.getInt(THREAD_OFFSET);
}
+ /** @return the wall clock time when the entry was written */
public long getTimeNanos() {
return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
+ mBuffer.getInt(NANOSECONDS_OFFSET);
}
+ /** @return the type tag code of the entry */
public int getTag() {
return mBuffer.getInt(TAG_OFFSET);
}
- /** @return one of Integer, Long, String, or List. */
+ /** @return one of Integer, Long, String, null, or Object[] of same. */
public synchronized Object getData() {
- mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET));
- mBuffer.position(DATA_START); // Just after the tag.
- return decodeObject();
- }
-
- public byte[] getRawData() {
- return mBuffer.array();
+ try {
+ mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET));
+ mBuffer.position(DATA_START); // Just after the tag.
+ return decodeObject();
+ } catch (IllegalArgumentException e) {
+ Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
+ return null;
+ } catch (BufferUnderflowException e) {
+ Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e);
+ return null;
+ }
}
/** @return the loggable item at the current position in mBuffer. */
private Object decodeObject() {
- if (mBuffer.remaining() < 1) return null;
- switch (mBuffer.get()) {
- case INT:
- if (mBuffer.remaining() < 4) return null;
+ byte type = mBuffer.get();
+ switch (type) {
+ case INT_TYPE:
return (Integer) mBuffer.getInt();
- case LONG:
- if (mBuffer.remaining() < 8) return null;
+ case LONG_TYPE:
return (Long) mBuffer.getLong();
- case STRING:
+ case STRING_TYPE:
try {
- if (mBuffer.remaining() < 4) return null;
int length = mBuffer.getInt();
- if (length < 0 || mBuffer.remaining() < length) return null;
int start = mBuffer.position();
mBuffer.position(start + length);
return new String(mBuffer.array(), start, length, "UTF-8");
} catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e); // UTF-8 is guaranteed.
+ Log.wtf(TAG, "UTF-8 is not supported", e);
+ return null;
}
- case LIST:
- if (mBuffer.remaining() < 1) return null;
+ case LIST_TYPE:
int length = mBuffer.get();
- if (length < 0) return null;
+ if (length < 0) length += 256; // treat as signed byte
Object[] array = new Object[length];
- for (int i = 0; i < length; ++i) {
- array[i] = decodeObject();
- if (array[i] == null) return null;
- }
- return new List(array);
+ for (int i = 0; i < length; ++i) array[i] = decodeObject();
+ return array;
default:
- return null;
+ throw new IllegalArgumentException("Unknown entry type: " + type);
}
}
}
@@ -236,46 +150,36 @@ public class EventLog {
// We assume that the native methods deal with any concurrency issues.
/**
- * Send an event log message.
- * @param tag An event identifer
+ * Record an event log message.
+ * @param tag The event type tag code
* @param value A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, int value);
/**
- * Send an event log message.
- * @param tag An event identifer
+ * Record an event log message.
+ * @param tag The event type tag code
* @param value A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, long value);
/**
- * Send an event log message.
- * @param tag An event identifer
+ * Record an event log message.
+ * @param tag The event type tag code
* @param str A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, String str);
/**
- * Send an event log message.
- * @param tag An event identifer
- * @param list A {@link List} to log
- * @return The number of bytes written
- */
- public static native int writeEvent(int tag, List list);
-
- /**
- * Send an event log message.
- * @param tag An event identifer
+ * Record an event log message.
+ * @param tag The event type tag code
* @param list A list of values to log
* @return The number of bytes written
*/
- public static int writeEvent(int tag, Object... list) {
- return writeEvent(tag, new List(list));
- }
+ public static native int writeEvent(int tag, Object... list);
/**
* Read events from the log, filtered by type.
@@ -287,11 +191,65 @@ public class EventLog {
throws IOException;
/**
- * Read events from a file.
- * @param path to read from
- * @param output container to add events into
- * @throws IOException if something goes wrong reading events
+ * Get the name associated with an event type tag code.
+ * @param tag code to look up
+ * @return the name of the tag, or null if no tag has that number
*/
- public static native void readEvents(String path, Collection<Event> output)
- throws IOException;
+ public static String getTagName(int tag) {
+ readTagsFile();
+ return sTagNames.get(tag);
+ }
+
+ /**
+ * Get the event type tag code associated with an event name.
+ * @param name of event to look up
+ * @return the tag code, or -1 if no tag has that name
+ */
+ public static int getTagCode(String name) {
+ readTagsFile();
+ Integer code = sTagCodes.get(name);
+ return code != null ? code : -1;
+ }
+
+ /**
+ * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
+ */
+ private static synchronized void readTagsFile() {
+ if (sTagCodes != null && sTagNames != null) return;
+
+ sTagCodes = new HashMap<String, Integer>();
+ sTagNames = new HashMap<Integer, String>();
+
+ Pattern comment = Pattern.compile(COMMENT_PATTERN);
+ Pattern tag = Pattern.compile(TAG_PATTERN);
+ BufferedReader reader = null;
+ String line;
+
+ try {
+ reader = new BufferedReader(new FileReader(TAGS_FILE), 256);
+ while ((line = reader.readLine()) != null) {
+ if (comment.matcher(line).matches()) continue;
+
+ Matcher m = tag.matcher(line);
+ if (!m.matches()) {
+ Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line);
+ continue;
+ }
+
+ try {
+ int num = Integer.parseInt(m.group(1));
+ String name = m.group(2);
+ sTagCodes.put(name, num);
+ sTagNames.put(num, name);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
+ }
+ }
+ } catch (IOException e) {
+ Log.wtf(TAG, "Error reading " + TAGS_FILE, e);
+ // Leave the maps existing but unpopulated
+ } finally {
+ try { if (reader != null) reader.close(); } catch (IOException e) {}
+ }
+ }
}
diff --git a/core/java/android/util/EventLogTags.java b/core/java/android/util/EventLogTags.java
index be905e3..5cf5332 100644
--- a/core/java/android/util/EventLogTags.java
+++ b/core/java/android/util/EventLogTags.java
@@ -25,16 +25,14 @@ import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-/** Parsed representation of /etc/event-log-tags. */
+/**
+ * @deprecated This class is no longer functional.
+ * Use {@link android.util.EventLog} instead.
+ */
public class EventLogTags {
- private final static String TAG = "EventLogTags";
-
- private final static String TAGS_FILE = "/etc/event-log-tags";
-
public static class Description {
public final int mTag;
public final String mName;
- // TODO: Parse parameter descriptions when anyone has a use for them.
Description(int tag, String name) {
mTag = tag;
@@ -42,49 +40,11 @@ public class EventLogTags {
}
}
- private final static Pattern COMMENT_PATTERN = Pattern.compile(
- "^\\s*(#.*)?$");
-
- private final static Pattern TAG_PATTERN = Pattern.compile(
- "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$");
+ public EventLogTags() throws IOException {}
- private final HashMap<String, Description> mNameMap =
- new HashMap<String, Description>();
-
- private final HashMap<Integer, Description> mTagMap =
- new HashMap<Integer, Description>();
-
- public EventLogTags() throws IOException {
- this(new BufferedReader(new FileReader(TAGS_FILE), 256));
- }
+ public EventLogTags(BufferedReader input) throws IOException {}
- public EventLogTags(BufferedReader input) throws IOException {
- String line;
- while ((line = input.readLine()) != null) {
- Matcher m = COMMENT_PATTERN.matcher(line);
- if (m.matches()) continue;
+ public Description get(String name) { return null; }
- m = TAG_PATTERN.matcher(line);
- if (m.matches()) {
- try {
- int tag = Integer.parseInt(m.group(1));
- Description d = new Description(tag, m.group(2));
- mNameMap.put(d.mName, d);
- mTagMap.put(d.mTag, d);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Error in event log tags entry: " + line, e);
- }
- } else {
- Log.e(TAG, "Can't parse event log tags entry: " + line);
- }
- }
- }
-
- public Description get(String name) {
- return mNameMap.get(name);
- }
-
- public Description get(int tag) {
- return mTagMap.get(tag);
- }
+ public Description get(int tag) { return null; }
}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 2572679..75b1b90 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -81,6 +81,13 @@ public final class Log {
*/
public static final int ASSERT = 7;
+ /**
+ * Exception class used to capture a stack trace in {@link #wtf()}.
+ */
+ private static class TerribleFailure extends Exception {
+ TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
+ }
+
private Log() {
}
@@ -170,24 +177,24 @@ public final class Log {
/**
* Checks to see whether or not a log for the specified tag is loggable at the specified level.
- *
+ *
* The default level of any tag is set to INFO. This means that any level above and including
* INFO will be logged. Before you make any calls to a logging method you should check to see
* if your tag should be logged. You can change the default level by setting a system property:
* 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
- * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPRESS will
+ * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
* turn off all logging for your tag. You can also create a local.prop file that with the
* following in it:
* 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
* and place that in /data/local.prop.
- *
+ *
* @param tag The tag to check.
* @param level The level to check.
* @return Whether or not that this is allowed to be logged.
* @throws IllegalArgumentException is thrown if the tag.length() > 23.
*/
public static native boolean isLoggable(String tag, int level);
-
+
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
@@ -216,9 +223,44 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- int r = println(ERROR, tag, msg + '\n' + getStackTraceString(tr));
- RuntimeInit.reportException(tag, tr, false); // asynchronous
- return r;
+ return println(ERROR, tag, msg + '\n' + getStackTraceString(tr));
+ }
+
+ /**
+ * What a Terrible Failure: Report a condition that should never happen.
+ * The error will always be logged at level ASSERT with the call stack.
+ * Depending on system configuration, a report may be added to the
+ * {@link android.os.DropBoxManager} and/or the process may be terminated
+ * immediately with an error dialog.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public static int wtf(String tag, String msg) {
+ return wtf(tag, msg, null);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, String)}, with an exception to log.
+ * @param tag Used to identify the source of a log message.
+ * @param tr An exception to log.
+ */
+ public static int wtf(String tag, Throwable tr) {
+ return wtf(tag, tr.getMessage(), tr);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, Throwable)}, with a message as well.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log. May be null.
+ */
+ public static int wtf(String tag, String msg, Throwable tr) {
+ tr = new TerribleFailure(msg, tr);
+ int bytes = println(ASSERT, tag, getStackTraceString(tr));
+ RuntimeInit.wtf(tag, tr);
+ return bytes;
}
/**
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 0fc70d5..4f496d7 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -27,7 +27,7 @@ import java.io.IOException;
import java.util.TimeZone;
import java.util.Date;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
/**
* A class containing utility methods related to time zones.
diff --git a/core/java/android/util/XmlPullAttributes.java b/core/java/android/util/XmlPullAttributes.java
index 12d6dd9..8f855cd 100644
--- a/core/java/android/util/XmlPullAttributes.java
+++ b/core/java/android/util/XmlPullAttributes.java
@@ -19,7 +19,7 @@ package android.util;
import org.xmlpull.v1.XmlPullParser;
import android.util.AttributeSet;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
/**
* Provides an implementation of AttributeSet on top of an XmlPullParser.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1fc3678..df4cab0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -379,7 +379,7 @@ import java.util.WeakHashMap;
* dimension, it can specify one of:
* <ul>
* <li> an exact number
- * <li>FILL_PARENT, which means the view wants to be as big as its parent
+ * <li>MATCH_PARENT, which means the view wants to be as big as its parent
* (minus padding)
* <li> WRAP_CONTENT, which means that the view wants to be just big enough to
* enclose its content (plus padding).
@@ -1494,6 +1494,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @hide
*/
static final int OPAQUE_MASK = 0x01800000;
+
+ /**
+ * Indicates a prepressed state;
+ * the short time between ACTION_DOWN and recognizing
+ * a 'real' press. Prepressed is used to recognize quick taps
+ * even when they are shorter than ViewConfiguration.getTapTimeout().
+ *
+ * @hide
+ */
+ private static final int PREPRESSED = 0x02000000;
/**
* The parent this view is attached to.
@@ -1722,6 +1732,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private int mNextFocusDownId = View.NO_ID;
private CheckForLongPress mPendingCheckForLongPress;
+ private CheckForTap mPendingCheckForTap = null;
+
private UnsetPressedState mUnsetPressedState;
/**
@@ -1762,6 +1774,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Special tree observer used when mAttachInfo is null.
*/
private ViewTreeObserver mFloatingTreeObserver;
+
+ /**
+ * Cache the touch slop from the context that created the view.
+ */
+ private int mTouchSlop;
// Used for debug only
static long sInstanceCount = 0;
@@ -1777,6 +1794,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mResources = context != null ? context.getResources() : null;
mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
++sInstanceCount;
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
/**
@@ -2589,8 +2607,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @param gainFocus True if the View has focus; false otherwise.
* @param direction The direction focus has moved when requestFocus()
* is called to give this view focus. Values are
- * View.FOCUS_UP, View.FOCUS_DOWN, View.FOCUS_LEFT or
- * View.FOCUS_RIGHT. It may not always apply, in which
+ * {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT} or
+ * {@link #FOCUS_RIGHT}. It may not always apply, in which
* case use the default.
* @param previouslyFocusedRect The rectangle, in this view's coordinate
* system, of the previously focused view. If applicable, this will be
@@ -2726,9 +2744,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
setPressed(false);
if (!mHasPerformedLongPress) {
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
}
}
}
@@ -3750,9 +3766,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
imm.focusOut(this);
}
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
onFocusLost();
} else if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
imm.focusIn(this);
@@ -3771,6 +3785,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Dispatch a view visibility change down the view hierarchy.
+ * ViewGroups should override to route to their children.
+ * @param changedView The view whose visibility changed. Could be 'this' or
+ * an ancestor view.
+ * @param visibility The new visibility of changedView.
+ */
+ protected void dispatchVisibilityChanged(View changedView, int visibility) {
+ onVisibilityChanged(changedView, visibility);
+ }
+
+ /**
+ * Called when the visibility of the view or an ancestor of the view is changed.
+ * @param changedView The view whose visibility changed. Could be 'this' or
+ * an ancestor view.
+ * @param visibility The new visibility of changedView.
+ */
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ }
+
+ /**
* Dispatch a window visibility change down the view hierarchy.
* ViewGroups should override to route to their children.
*
@@ -3935,7 +3969,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
(event.getRepeatCount() == 0)) {
setPressed(true);
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
- postCheckForLongClick();
+ postCheckForLongClick(0);
}
return true;
}
@@ -3978,9 +4012,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
result = performClick();
}
@@ -4160,7 +4192,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
- if ((mPrivateFlags & PRESSED) != 0) {
+ boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
+ if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
@@ -4170,9 +4203,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
@@ -4184,24 +4215,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mUnsetPressedState = new UnsetPressedState();
}
- if (!post(mUnsetPressedState)) {
+ if (prepressed) {
+ mPrivateFlags |= PRESSED;
+ refreshDrawableState();
+ postDelayed(mUnsetPressedState,
+ ViewConfiguration.getPressedStateDuration());
+ } else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
+ removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
- postCheckForLongClick();
+ if (mPendingCheckForTap == null) {
+ mPendingCheckForTap = new CheckForTap();
}
+ mPrivateFlags |= PREPRESSED;
+ postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
+ removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
@@ -4209,27 +4247,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
- int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
+ removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
- // Remove any future long press checks
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ // Remove any future long press/tap checks
+ removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
- } else {
- // Inside button
- if ((mPrivateFlags & PRESSED) == 0) {
- // Need to switch from not pressed to pressed
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- }
}
break;
}
@@ -4240,15 +4270,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Remove the longpress detection timer.
+ */
+ private void removeLongPressCallback() {
+ if (mPendingCheckForLongPress != null) {
+ removeCallbacks(mPendingCheckForLongPress);
+ }
+ }
+
+ /**
+ * Remove the tap detection timer.
+ */
+ private void removeTapCallback() {
+ if (mPendingCheckForTap != null) {
+ mPrivateFlags &= ~PREPRESSED;
+ removeCallbacks(mPendingCheckForTap);
+ }
+ }
+
+ /**
* Cancels a pending long press. Your subclass can use this if you
* want the context menu to come up if the user presses and holds
* at the same place, but you don't want it to come up if they press
* and then move around enough to cause scrolling.
*/
public void cancelLongPress() {
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
}
/**
@@ -4349,6 +4396,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
}
+ if ((changed & VISIBILITY_MASK) != 0) {
+ dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK));
+ }
+
if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
destroyDrawingCache();
}
@@ -5745,9 +5796,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @see #onAttachedToWindow()
*/
protected void onDetachedFromWindow() {
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
destroyDrawingCache();
}
@@ -5961,7 +6010,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= SAVE_STATE_CALLED;
if (state != BaseSavedState.EMPTY_STATE && state != null) {
- throw new IllegalArgumentException("Wrong state class -- expecting View State");
+ throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ + "received " + state.getClass().toString() + " instead. This usually happens "
+ + "when two views of different type have the same id in the same hierarchy. "
+ + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ + "other views do not use the same id.");
}
}
@@ -8404,14 +8457,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
}
- private void postCheckForLongClick() {
+ private void postCheckForLongClick(int delayOffset) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
- postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
+ postDelayed(mPendingCheckForLongPress,
+ ViewConfiguration.getLongPressTimeout() - delayOffset);
}
private static int[] stateSetUnion(final int[] stateSet1,
@@ -8588,6 +8642,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
+
+ private final class CheckForTap implements Runnable {
+ public void run() {
+ mPrivateFlags &= ~PREPRESSED;
+ mPrivateFlags |= PRESSED;
+ refreshDrawableState();
+ if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
+ postCheckForLongClick(ViewConfiguration.getTapTimeout());
+ }
+ }
+ }
/**
* Interface definition for a callback to be invoked when a key event is
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 993048f..2344c42 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -49,7 +49,7 @@ public class ViewConfiguration {
* Defines the duration in milliseconds of the pressed state in child
* components.
*/
- private static final int PRESSED_STATE_DURATION = 85;
+ private static final int PRESSED_STATE_DURATION = 125;
/**
* Defines the duration in milliseconds before a press turns into
@@ -69,7 +69,7 @@ public class ViewConfiguration {
* is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
*/
- private static final int TAP_TIMEOUT = 100;
+ private static final int TAP_TIMEOUT = 115;
/**
* Defines the duration in milliseconds we will wait to see if a touch event
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 4baf612..3ebe1c2 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -702,7 +702,7 @@ public class ViewDebug {
if (parameter.indexOf('@') != -1) {
final String[] ids = parameter.split("@");
final String className = ids[0];
- final int hashCode = Integer.parseInt(ids[1], 16);
+ final int hashCode = (int) Long.parseLong(ids[1], 16);
View view = root.getRootView();
if (view instanceof ViewGroup) {
@@ -910,7 +910,7 @@ public class ViewDebug {
private static void dump(View root, OutputStream clientStream) throws IOException {
BufferedWriter out = null;
try {
- out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+ out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
View view = root.getRootView();
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
@@ -1300,7 +1300,7 @@ public class ViewDebug {
}
}
- private static Object resolveId(Context context, int id) {
+ static Object resolveId(Context context, int id) {
Object fieldValue;
final Resources resources = context.getResources();
if (id >= 0) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e2f15c7..763f273 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -684,6 +684,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@inheritDoc}
*/
@Override
+ protected void dispatchVisibilityChanged(View changedView, int visibility) {
+ super.dispatchVisibilityChanged(changedView, visibility);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchVisibilityChanged(changedView, visibility);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public void dispatchWindowVisibilityChanged(int visibility) {
super.dispatchWindowVisibilityChanged(visibility);
final int count = mChildrenCount;
@@ -3057,7 +3070,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.FILL_PARENT) {
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
@@ -3075,7 +3088,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.FILL_PARENT) {
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
@@ -3094,7 +3107,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.FILL_PARENT) {
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
@@ -3349,7 +3362,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* for both width and height. For each dimension, it can specify one of:
* <ul>
* <li> an exact number
- * <li>FILL_PARENT, which means the view wants to be as big as its parent
+ * <li>MATCH_PARENT, which means the view wants to be as big as its parent
* (minus padding)
* <li> WRAP_CONTENT, which means that the view wants to be just big enough
* to enclose its content (plus padding)
@@ -3363,14 +3376,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public static class LayoutParams {
/**
- * Special value for the height or width requested by a View.
- * FILL_PARENT means that the view wants to fill the available space
- * within the parent, taking the parent's padding into account.
+ * This value has the same meaning as {@link #MATCH_PARENT} but has
+ * been deprecated.
*/
+ @SuppressWarnings({"UnusedDeclaration"})
+ @Deprecated
public static final int FILL_PARENT = -1;
/**
* Special value for the height or width requested by a View.
+ * MATCH_PARENT means that the view wants to be as bigas its parent,
+ * minus the parent's padding, if any.
+ */
+ public static final int MATCH_PARENT = -1;
+
+ /**
+ * Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
@@ -3378,20 +3399,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Information about how wide the view wants to be. Can be an exact
- * size, or one of the constants FILL_PARENT or WRAP_CONTENT.
+ * size, or one of the constants MATCH_PARENT or WRAP_CONTENT.
*/
@ViewDebug.ExportedProperty(mapping = {
- @ViewDebug.IntToString(from = FILL_PARENT, to = "FILL_PARENT"),
+ @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
@ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
})
public int width;
/**
* Information about how tall the view wants to be. Can be an exact
- * size, or one of the constants FILL_PARENT or WRAP_CONTENT.
+ * size, or one of the constants MATCH_PARENT or WRAP_CONTENT.
*/
@ViewDebug.ExportedProperty(mapping = {
- @ViewDebug.IntToString(from = FILL_PARENT, to = "FILL_PARENT"),
+ @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
@ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
})
public int height;
@@ -3408,9 +3429,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* <ul>
* <li><code>layout_width</code>: the width, either an exact value,
- * {@link #WRAP_CONTENT} or {@link #FILL_PARENT}</li>
+ * {@link #WRAP_CONTENT} or {@link #MATCH_PARENT}</li>
* <li><code>layout_height</code>: the height, either an exact value,
- * {@link #WRAP_CONTENT} or {@link #FILL_PARENT}</li>
+ * {@link #WRAP_CONTENT} or {@link #MATCH_PARENT}</li>
* </ul>
*
* @param c the application environment
@@ -3429,9 +3450,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Creates a new set of layout parameters with the specified width
* and height.
*
- * @param width the width, either {@link #FILL_PARENT},
+ * @param width the width, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #FILL_PARENT},
+ * @param height the height, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
*/
public LayoutParams(int width, int height) {
@@ -3494,8 +3515,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (size == WRAP_CONTENT) {
return "wrap-content";
}
- if (size == FILL_PARENT) {
- return "fill-parent";
+ if (size == MATCH_PARENT) {
+ return "match-parent";
}
return String.valueOf(size);
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 4e12250..094b7dd 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -68,6 +68,7 @@ public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
private static final String TAG = "ViewRoot";
private static final boolean DBG = false;
+ private static final boolean SHOW_FPS = false;
@SuppressWarnings({"ConstantConditionalExpression"})
private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV;
/** @noinspection PointlessBooleanExpression*/
@@ -1166,7 +1167,7 @@ public final class ViewRoot extends Handler implements ViewParent,
int measureSpec;
switch (rootDimension) {
- case ViewGroup.LayoutParams.FILL_PARENT:
+ case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
@@ -1244,7 +1245,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
checkEglErrors();
- if (Config.DEBUG && ViewDebug.showFps) {
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
@@ -1356,7 +1357,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
}
- if (Config.DEBUG && ViewDebug.showFps) {
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java
index 703a38f..d5e9af4 100644
--- a/core/java/android/view/ViewStub.java
+++ b/core/java/android/view/ViewStub.java
@@ -207,9 +207,11 @@ public final class ViewStub extends View {
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
- } else if (visibility == VISIBLE || visibility == INVISIBLE) {
+ } else {
super.setVisibility(visibility);
- inflate();
+ if (visibility == VISIBLE || visibility == INVISIBLE) {
+ inflate();
+ }
}
}
@@ -244,7 +246,7 @@ public final class ViewStub extends View {
parent.addView(view, index);
}
- mInflatedViewRef = new WeakReference(view);
+ mInflatedViewRef = new WeakReference<View>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 1932765..7dd5085 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -484,7 +484,7 @@ public abstract class Window {
/**
* Set the width and height layout parameters of the window. The default
- * for both of these is FILL_PARENT; you can change them to WRAP_CONTENT to
+ * for both of these is MATCH_PARENT; you can change them to WRAP_CONTENT to
* make a window that is not full-screen.
*
* @param width The desired layout width of the window.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 6696533..8e15f89 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -649,7 +649,28 @@ public interface WindowManager extends ViewManager {
* be cleared automatically after the window is displayed.
*/
public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
-
+
+ /**
+ * Default value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the brightness value is not overridden for this window
+ * and normal brightness policy should be used.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
+
+ /**
+ * Value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the screen or button backlight brightness should be set
+ * to the lowest value when this window is in front.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
+
+ /**
+ * Value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the screen or button backlight brightness should be set
+ * to the hightest value when this window is in front.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
+
/**
* Desired operating mode for any soft input area. May any combination
* of:
@@ -717,9 +738,17 @@ public interface WindowManager extends ViewManager {
* preferred screen brightness. 0 to 1 adjusts the brightness from
* dark to full bright.
*/
- public float screenBrightness = -1.0f;
+ public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
/**
+ * This can be used to override the standard behavior of the button and
+ * keyboard backlights. A value of less than 0, the default, means to
+ * use the standard backlight behavior. 0 to 1 adjusts the brightness
+ * from dark to full bright.
+ */
+ public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
+
+ /**
* Identifier for this window. This will usually be filled in for
* you.
*/
@@ -742,26 +771,26 @@ public interface WindowManager extends ViewManager {
public LayoutParams() {
- super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
public LayoutParams(int _type) {
- super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
format = PixelFormat.OPAQUE;
}
public LayoutParams(int _type, int _flags) {
- super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
flags = _flags;
format = PixelFormat.OPAQUE;
}
public LayoutParams(int _type, int _flags, int _format) {
- super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
flags = _flags;
format = _format;
@@ -816,6 +845,7 @@ public interface WindowManager extends ViewManager {
out.writeFloat(alpha);
out.writeFloat(dimAmount);
out.writeFloat(screenBrightness);
+ out.writeFloat(buttonBrightness);
out.writeStrongBinder(token);
out.writeString(packageName);
TextUtils.writeToParcel(mTitle, out, parcelableFlags);
@@ -851,6 +881,7 @@ public interface WindowManager extends ViewManager {
alpha = in.readFloat();
dimAmount = in.readFloat();
screenBrightness = in.readFloat();
+ buttonBrightness = in.readFloat();
token = in.readStrongBinder();
packageName = in.readString();
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -870,6 +901,8 @@ public interface WindowManager extends ViewManager {
public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
+ /** {@hide} */
+ public static final int BUTTON_BRIGHTNESS_CHANGED = 1<<12;
// internal buffer to backup/restore parameters under compatibility mode.
private int[] mCompatibilityParamsBackup = null;
@@ -971,6 +1004,10 @@ public interface WindowManager extends ViewManager {
screenBrightness = o.screenBrightness;
changes |= SCREEN_BRIGHTNESS_CHANGED;
}
+ if (buttonBrightness != o.buttonBrightness) {
+ buttonBrightness = o.buttonBrightness;
+ changes |= BUTTON_BRIGHTNESS_CHANGED;
+ }
if (screenOrientation != o.screenOrientation) {
screenOrientation = o.screenOrientation;
@@ -999,9 +1036,9 @@ public interface WindowManager extends ViewManager {
sb.append(',');
sb.append(y);
sb.append(")(");
- sb.append((width==FILL_PARENT?"fill":(width==WRAP_CONTENT?"wrap":width)));
+ sb.append((width== MATCH_PARENT ?"fill":(width==WRAP_CONTENT?"wrap":width)));
sb.append('x');
- sb.append((height==FILL_PARENT?"fill":(height==WRAP_CONTENT?"wrap":height)));
+ sb.append((height== MATCH_PARENT ?"fill":(height==WRAP_CONTENT?"wrap":height)));
sb.append(")");
if (softInputMode != 0) {
sb.append(" sim=#");
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 083793b..bbe9c1f 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -237,25 +237,6 @@ public interface WindowManagerPolicy {
public boolean hasAppShownWindows();
/**
- * Return true if the application token has been asked to display an
- * app starting icon as the application is starting up.
- *
- * @return Returns true if setAppStartingIcon() was called for this
- * window's token.
- */
- public boolean hasAppStartingIcon();
-
- /**
- * Return the Window that is being displayed as this window's
- * application token is being started.
- *
- * @return Returns the currently displayed starting window, or null if
- * it was not requested, has not yet been displayed, or has
- * been removed.
- */
- public WindowState getAppStartingWindow();
-
- /**
* Is this window visible? It is not visible if there is no
* surface, or we are in the process of running an exit animation
* that will remove the surface.
@@ -681,6 +662,14 @@ public interface WindowManagerPolicy {
public boolean finishAnimationLw();
/**
+ * Return true if it is okay to perform animations for an app transition
+ * that is about to occur. You may return false for this if, for example,
+ * the lock screen is currently displayed so the switch should happen
+ * immediately.
+ */
+ public boolean allowAppAnimationsLw();
+
+ /**
* Called after the screen turns off.
*
* @param why {@link #OFF_BECAUSE_OF_USER} or
@@ -694,6 +683,11 @@ public interface WindowManagerPolicy {
public void screenTurnedOn();
/**
+ * Return whether the screen is currently on.
+ */
+ public boolean isScreenOn();
+
+ /**
* Perform any initial processing of a low-level input event before the
* window manager handles special keys and generates a high-level event
* that is dispatched to the application.
@@ -795,11 +789,6 @@ public interface WindowManagerPolicy {
void exitKeyguardSecurely(OnKeyguardExitResult callback);
/**
- * Return if keyguard is currently showing.
- */
- public boolean keyguardIsShowingTq();
-
- /**
* inKeyguardRestrictedKeyInputMode
*
* if keyguard screen is showing or in restricted key input mode (i.e. in
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 13606e7..2aba60b 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -33,13 +33,12 @@ import android.util.Log;
public abstract class WindowOrientationListener {
private static final String TAG = "WindowOrientationListener";
private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean localLOGV = DEBUG || Config.DEBUG;
private SensorManager mSensorManager;
private boolean mEnabled = false;
private int mRate;
private Sensor mSensor;
- private SensorEventListener mSensorEventListener;
- private int mSensorRotation = -1;
+ private SensorEventListenerImpl mSensorEventListener;
/**
* Creates a new WindowOrientationListener.
@@ -80,7 +79,6 @@ public abstract class WindowOrientationListener {
}
if (mEnabled == false) {
if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled");
- mSensorRotation = -1;
mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
mEnabled = true;
}
@@ -96,23 +94,22 @@ public abstract class WindowOrientationListener {
}
if (mEnabled == true) {
if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled");
- mSensorRotation = -1;
mSensorManager.unregisterListener(mSensorEventListener);
mEnabled = false;
}
}
public int getCurrentRotation() {
- return mSensorRotation;
+ if (mEnabled) {
+ return mSensorEventListener.getCurrentRotation();
+ }
+ return -1;
}
class SensorEventListenerImpl implements SensorEventListener {
private static final int _DATA_X = 0;
private static final int _DATA_Y = 1;
private static final int _DATA_Z = 2;
- // Angle around x-axis thats considered almost perfect vertical to hold
- // the device
- private static final int PIVOT = 20;
// Angle around x-asis that's considered almost too vertical. Beyond
// this angle will not result in any orientation changes. f phone faces uses,
// the device is leaning backward.
@@ -121,30 +118,61 @@ public abstract class WindowOrientationListener {
// angle will not result in any orientation changes. If phone faces uses,
// the device is leaning forward.
private static final int PIVOT_LOWER = -10;
- // Upper threshold limit for switching from portrait to landscape
- private static final int PL_UPPER = 295;
- // Lower threshold limit for switching from landscape to portrait
- private static final int LP_LOWER = 320;
- // Lower threshold limt for switching from portrait to landscape
- private static final int PL_LOWER = 270;
- // Upper threshold limit for switching from landscape to portrait
- private static final int LP_UPPER = 359;
- // Minimum angle which is considered landscape
- private static final int LANDSCAPE_LOWER = 235;
- // Minimum angle which is considered portrait
- private static final int PORTRAIT_LOWER = 60;
-
- // Internal value used for calculating linear variant
- private static final float PL_LF_UPPER =
- ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT_UPPER-PIVOT));
- private static final float PL_LF_LOWER =
- ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT-PIVOT_LOWER));
- // Internal value used for calculating linear variant
- private static final float LP_LF_UPPER =
- ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT_UPPER-PIVOT));
- private static final float LP_LF_LOWER =
- ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT-PIVOT_LOWER));
+ static final int ROTATION_0 = 0;
+ static final int ROTATION_90 = 1;
+ static final int ROTATION_180 = 2;
+ static final int ROTATION_270 = 3;
+ int mRotation = ROTATION_0;
+
+ // Threshold values defined for device rotation positions
+ // follow order ROTATION_0 .. ROTATION_270
+ final int THRESHOLDS[][][] = new int[][][] {
+ {{60, 135}, {135, 225}, {225, 300}},
+ {{0, 45}, {45, 135}, {135, 210}, {330, 360}},
+ {{0, 45}, {45, 120}, {240, 315}, {315, 360}},
+ {{0, 30}, {150, 225}, {225, 315}, {315, 360}}
+ };
+
+ // Transform rotation ranges based on THRESHOLDS. This
+ // has to be in step with THESHOLDS
+ final int ROTATE_TO[][] = new int[][] {
+ {ROTATION_270, ROTATION_180, ROTATION_90},
+ {ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0},
+ {ROTATION_0, ROTATION_270, ROTATION_90, ROTATION_0},
+ {ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0}
+ };
+
+ // Mapping into actual Surface rotation values
+ final int TRANSFORM_ROTATIONS[] = new int[]{Surface.ROTATION_0,
+ Surface.ROTATION_90, Surface.ROTATION_180, Surface.ROTATION_270};
+
+ int getCurrentRotation() {
+ return TRANSFORM_ROTATIONS[mRotation];
+ }
+ private void calculateNewRotation(int orientation, int zyangle) {
+ if (localLOGV) Log.i(TAG, orientation + ", " + zyangle + ", " + mRotation);
+ int rangeArr[][] = THRESHOLDS[mRotation];
+ int row = -1;
+ for (int i = 0; i < rangeArr.length; i++) {
+ if ((orientation >= rangeArr[i][0]) && (orientation < rangeArr[i][1])) {
+ row = i;
+ break;
+ }
+ }
+ if (row != -1) {
+ // Find new rotation based on current rotation value.
+ // This also takes care of irregular rotations as well.
+ int rotation = ROTATE_TO[mRotation][row];
+ if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
+ if (rotation != mRotation) {
+ mRotation = rotation;
+ // Trigger orientation change
+ onOrientationChanged(TRANSFORM_ROTATIONS[rotation]);
+ }
+ }
+ }
+
public void onSensorChanged(SensorEvent event) {
float[] values = event.values;
float X = values[_DATA_X];
@@ -153,53 +181,19 @@ public abstract class WindowOrientationListener {
float OneEightyOverPi = 57.29577957855f;
float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z);
float zyangle = (float)Math.asin(Z/gravity)*OneEightyOverPi;
- int rotation = -1;
if ((zyangle <= PIVOT_UPPER) && (zyangle >= PIVOT_LOWER)) {
// Check orientation only if the phone is flat enough
// Don't trust the angle if the magnitude is small compared to the y value
float angle = (float)Math.atan2(Y, -X) * OneEightyOverPi;
- int orientation = 90 - (int)Math.round(angle);
+ int orientation = 90 - Math.round(angle);
// normalize to 0 - 359 range
while (orientation >= 360) {
orientation -= 360;
- }
+ }
while (orientation < 0) {
orientation += 360;
}
- // Orientation values between LANDSCAPE_LOWER and PL_LOWER
- // are considered landscape.
- // Ignore orientation values between 0 and LANDSCAPE_LOWER
- // For orientation values between LP_UPPER and PL_LOWER,
- // the threshold gets set linearly around PIVOT.
- if ((orientation >= PL_LOWER) && (orientation <= LP_UPPER)) {
- float threshold;
- float delta = zyangle - PIVOT;
- if (mSensorRotation == Surface.ROTATION_90) {
- if (delta < 0) {
- // Delta is negative
- threshold = LP_LOWER - (LP_LF_LOWER * delta);
- } else {
- threshold = LP_LOWER + (LP_LF_UPPER * delta);
- }
- rotation = (orientation >= threshold) ? Surface.ROTATION_0 : Surface.ROTATION_90;
- } else {
- if (delta < 0) {
- // Delta is negative
- threshold = PL_UPPER+(PL_LF_LOWER * delta);
- } else {
- threshold = PL_UPPER-(PL_LF_UPPER * delta);
- }
- rotation = (orientation <= threshold) ? Surface.ROTATION_90: Surface.ROTATION_0;
- }
- } else if ((orientation >= LANDSCAPE_LOWER) && (orientation < LP_LOWER)) {
- rotation = Surface.ROTATION_90;
- } else if ((orientation >= PL_UPPER) || (orientation <= PORTRAIT_LOWER)) {
- rotation = Surface.ROTATION_0;
- }
- if ((rotation != -1) && (rotation != mSensorRotation)) {
- mSensorRotation = rotation;
- onOrientationChanged(mSensorRotation);
- }
+ calculateNewRotation(orientation, Math.round(zyangle));
}
}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 9456ae1..1337bed 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -19,17 +19,23 @@ package android.webkit;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.AssetManager;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.ParseException;
+import android.net.Uri;
import android.net.WebAddress;
import android.net.http.SslCertificate;
import android.os.Handler;
import android.os.Message;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.util.TypedValue;
+import android.view.Surface;
+import android.view.WindowOrientationListener;
import junit.framework.Assert;
+import java.io.InputStream;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
@@ -63,9 +69,14 @@ class BrowserFrame extends Handler {
// Attached Javascript interfaces
private Map<String, Object> mJSInterfaceMap;
+ // Orientation listener
+ private WindowOrientationListener mOrientationListener;
+
// message ids
// a message posted when a frame loading is completed
static final int FRAME_COMPLETED = 1001;
+ // orientation change message
+ static final int ORIENTATION_CHANGED = 1002;
// a message posted when the user decides the policy
static final int POLICY_FUNCTION = 1003;
@@ -101,10 +112,13 @@ class BrowserFrame extends Handler {
*/
public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
WebSettings settings, Map<String, Object> javascriptInterfaces) {
+
+ Context appContext = context.getApplicationContext();
+
// Create a global JWebCoreJavaBridge to handle timers and
// cookies in the WebCore thread.
if (sJavaBridge == null) {
- sJavaBridge = new JWebCoreJavaBridge(context);
+ sJavaBridge = new JWebCoreJavaBridge(appContext);
// set WebCore native cache size
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
@@ -114,18 +128,18 @@ class BrowserFrame extends Handler {
sJavaBridge.setCacheSize(4 * 1024 * 1024);
}
// initialize CacheManager
- CacheManager.init(context);
+ CacheManager.init(appContext);
// create CookieSyncManager with current Context
- CookieSyncManager.createInstance(context);
+ CookieSyncManager.createInstance(appContext);
// create PluginManager with current Context
- PluginManager.getInstance(context);
+ PluginManager.getInstance(appContext);
}
mJSInterfaceMap = javascriptInterfaces;
mSettings = settings;
mContext = context;
mCallbackProxy = proxy;
- mDatabase = WebViewDatabase.getInstance(context);
+ mDatabase = WebViewDatabase.getInstance(appContext);
mWebViewCore = w;
AssetManager am = context.getAssets();
@@ -134,6 +148,31 @@ class BrowserFrame extends Handler {
if (DebugFlags.BROWSER_FRAME) {
Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
}
+
+ mOrientationListener = new WindowOrientationListener(context) {
+ @Override
+ public void onOrientationChanged(int orientation) {
+ switch (orientation) {
+ case Surface.ROTATION_90:
+ orientation = 90;
+ break;
+ case Surface.ROTATION_180:
+ orientation = 180;
+ break;
+ case Surface.ROTATION_270:
+ orientation = -90;
+ break;
+ case Surface.ROTATION_0:
+ orientation = 0;
+ break;
+ default:
+ break;
+ }
+ sendMessage(
+ obtainMessage(ORIENTATION_CHANGED, orientation, 0));
+ }
+ };
+ mOrientationListener.enable();
}
/**
@@ -300,6 +339,7 @@ class BrowserFrame extends Handler {
// loadType is not used yet
if (isMainFrame) {
mCommitted = true;
+ mWebViewCore.getWebView().mViewManager.postResetStateAll();
}
}
@@ -338,6 +378,7 @@ class BrowserFrame extends Handler {
* Destroy all native components of the BrowserFrame.
*/
public void destroy() {
+ mOrientationListener.disable();
nativeDestroyFrame();
removeCallbacksAndMessages(null);
}
@@ -372,6 +413,11 @@ class BrowserFrame extends Handler {
break;
}
+ case ORIENTATION_CHANGED: {
+ nativeOrientationChanged(msg.arg1);
+ break;
+ }
+
default:
break;
}
@@ -463,6 +509,63 @@ class BrowserFrame extends Handler {
}
/**
+ * Called by JNI. Given a URI, find the associated file and return its size
+ * @param uri A String representing the URI of the desired file.
+ * @return int The size of the given file.
+ */
+ private int getFileSize(String uri) {
+ int size = 0;
+ Cursor cursor = mContext.getContentResolver().query(Uri.parse(uri),
+ new String[] { OpenableColumns.SIZE },
+ null,
+ null,
+ null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ size = cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Called by JNI. Given a URI, a buffer, and an offset into the buffer,
+ * copy the resource into buffer.
+ * @param uri A String representing the URI of the desired file.
+ * @param buffer The byte array to copy the data into.
+ * @param offset The offet into buffer to place the data.
+ * @param expectedSize The size that the buffer has allocated for this file.
+ * @return int The size of the given file, or zero if it fails.
+ */
+ private int getFile(String uri, byte[] buffer, int offset,
+ int expectedSize) {
+ int size = 0;
+ try {
+ InputStream stream = mContext.getContentResolver()
+ .openInputStream(Uri.parse(uri));
+ size = stream.available();
+ if (size <= expectedSize && buffer != null
+ && buffer.length - offset >= size) {
+ stream.read(buffer, offset, size);
+ } else {
+ size = 0;
+ }
+ stream.close();
+ } catch (java.io.FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException:" + e);
+ size = 0;
+ } catch (java.io.IOException e2) {
+ Log.e(LOGTAG, "IOException: " + e2);
+ size = 0;
+ }
+ return size;
+ }
+
+ /**
* Start loading a resource.
* @param loaderHandle The native ResourceLoader that is the target of the
* data.
@@ -480,7 +583,10 @@ class BrowserFrame extends Handler {
String method,
HashMap headers,
byte[] postData,
+ long postDataIdentifier,
int cacheMode,
+ boolean mainResource,
+ boolean userGesture,
boolean synchronous) {
PerfChecker checker = new PerfChecker();
@@ -547,12 +653,14 @@ class BrowserFrame extends Handler {
if (DebugFlags.BROWSER_FRAME) {
Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
+ method + ", postData=" + postData + ", isMainFramePage="
- + isMainFramePage);
+ + isMainFramePage + ", mainResource=" + mainResource
+ + ", userGesture=" + userGesture);
}
// Create a LoadListener
- LoadListener loadListener = LoadListener.getLoadListener(mContext, this, url,
- loaderHandle, synchronous, isMainFramePage);
+ LoadListener loadListener = LoadListener.getLoadListener(mContext,
+ this, url, loaderHandle, synchronous, isMainFramePage,
+ mainResource, userGesture, postDataIdentifier);
mCallbackProxy.onLoadResource(url);
@@ -675,10 +783,13 @@ class BrowserFrame extends Handler {
return mSettings.getUserAgentString();
}
- // these ids need to be in sync with enum RAW_RES_ID in WebFrame
+ // These ids need to be in sync with enum rawResId in PlatformBridge.h
private static final int NODOMAIN = 1;
private static final int LOADERROR = 2;
private static final int DRAWABLEDIR = 3;
+ private static final int FILE_UPLOAD_LABEL = 4;
+ private static final int RESET_LABEL = 5;
+ private static final int SUBMIT_LABEL = 6;
String getRawResFilename(int id) {
int resid;
@@ -696,6 +807,18 @@ class BrowserFrame extends Handler {
resid = com.android.internal.R.drawable.btn_check_off;
break;
+ case FILE_UPLOAD_LABEL:
+ return mContext.getResources().getString(
+ com.android.internal.R.string.upload_file);
+
+ case RESET_LABEL:
+ return mContext.getResources().getString(
+ com.android.internal.R.string.reset);
+
+ case SUBMIT_LABEL:
+ return mContext.getResources().getString(
+ com.android.internal.R.string.submit);
+
default:
Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
return "";
@@ -830,4 +953,6 @@ class BrowserFrame extends Handler {
* returns null.
*/
private native HashMap getFormTextData();
+
+ private native void nativeOrientationChanged(int orientation);
}
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
index 145411c..334526b 100644
--- a/core/java/android/webkit/ByteArrayBuilder.java
+++ b/core/java/android/webkit/ByteArrayBuilder.java
@@ -16,6 +16,8 @@
package android.webkit;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
import java.util.LinkedList;
import java.util.ListIterator;
@@ -23,47 +25,37 @@ import java.util.ListIterator;
them back out. It does not optimize for returning the result in a
single array, though this is supported in the API. It is fastest
if the retrieval can be done via iterating through chunks.
-
- Things to add:
- - consider dynamically increasing our min_capacity,
- as we see mTotalSize increase
*/
class ByteArrayBuilder {
private static final int DEFAULT_CAPACITY = 8192;
- private LinkedList<Chunk> mChunks;
-
- /** free pool */
- private LinkedList<Chunk> mPool;
+ // Global pool of chunks to be used by other ByteArrayBuilders.
+ private static final LinkedList<SoftReference<Chunk>> sPool =
+ new LinkedList<SoftReference<Chunk>>();
+ // Reference queue for processing gc'd entries.
+ private static final ReferenceQueue<Chunk> sQueue =
+ new ReferenceQueue<Chunk>();
- private int mMinCapacity;
+ private LinkedList<Chunk> mChunks;
public ByteArrayBuilder() {
- init(0);
- }
-
- public ByteArrayBuilder(int minCapacity) {
- init(minCapacity);
- }
-
- private void init(int minCapacity) {
mChunks = new LinkedList<Chunk>();
- mPool = new LinkedList<Chunk>();
-
- if (minCapacity <= 0) {
- minCapacity = DEFAULT_CAPACITY;
- }
- mMinCapacity = minCapacity;
- }
-
- public void append(byte[] array) {
- append(array, 0, array.length);
}
public synchronized void append(byte[] array, int offset, int length) {
while (length > 0) {
- Chunk c = appendChunk(length);
+ Chunk c = null;
+ if (mChunks.isEmpty()) {
+ c = obtainChunk(length);
+ mChunks.addLast(c);
+ } else {
+ c = mChunks.getLast();
+ if (c.mLength == c.mArray.length) {
+ c = obtainChunk(length);
+ mChunks.addLast(c);
+ }
+ }
int amount = Math.min(length, c.mArray.length - c.mLength);
System.arraycopy(array, offset, c.mArray, c.mLength, amount);
c.mLength += amount;
@@ -75,7 +67,7 @@ class ByteArrayBuilder {
/**
* The fastest way to retrieve the data is to iterate through the
* chunks. This returns the first chunk. Note: this pulls the
- * chunk out of the queue. The caller must call releaseChunk() to
+ * chunk out of the queue. The caller must call Chunk.release() to
* dispose of it.
*/
public synchronized Chunk getFirstChunk() {
@@ -83,23 +75,11 @@ class ByteArrayBuilder {
return mChunks.removeFirst();
}
- /**
- * recycles chunk
- */
- public synchronized void releaseChunk(Chunk c) {
- c.mLength = 0;
- mPool.addLast(c);
- }
-
- public boolean isEmpty() {
+ public synchronized boolean isEmpty() {
return mChunks.isEmpty();
}
- public int size() {
- return mChunks.size();
- }
-
- public int getByteSize() {
+ public synchronized int getByteSize() {
int total = 0;
ListIterator<Chunk> it = mChunks.listIterator(0);
while (it.hasNext()) {
@@ -112,37 +92,40 @@ class ByteArrayBuilder {
public synchronized void clear() {
Chunk c = getFirstChunk();
while (c != null) {
- releaseChunk(c);
+ c.release();
c = getFirstChunk();
}
}
- private Chunk appendChunk(int length) {
- if (length < mMinCapacity) {
- length = mMinCapacity;
- }
-
- Chunk c;
- if (mChunks.isEmpty()) {
- c = obtainChunk(length);
- } else {
- c = mChunks.getLast();
- if (c.mLength == c.mArray.length) {
- c = obtainChunk(length);
+ // Must be called with lock held on sPool.
+ private void processPoolLocked() {
+ while (true) {
+ SoftReference<Chunk> entry = (SoftReference<Chunk>) sQueue.poll();
+ if (entry == null) {
+ break;
}
+ sPool.remove(entry);
}
- return c;
}
private Chunk obtainChunk(int length) {
- Chunk c;
- if (mPool.isEmpty()) {
- c = new Chunk(length);
- } else {
- c = mPool.removeFirst();
+ // Correct a small length.
+ if (length < DEFAULT_CAPACITY) {
+ length = DEFAULT_CAPACITY;
+ }
+ synchronized (sPool) {
+ // Process any queued references and remove them from the pool.
+ processPoolLocked();
+ if (!sPool.isEmpty()) {
+ Chunk c = sPool.removeFirst().get();
+ // The first item may have been queued after processPoolLocked
+ // so check for null.
+ if (c != null) {
+ return c;
+ }
+ }
+ return new Chunk(length);
}
- mChunks.addLast(c);
- return c;
}
public static class Chunk {
@@ -153,5 +136,19 @@ class ByteArrayBuilder {
mArray = new byte[length];
mLength = 0;
}
+
+ /**
+ * Release the chunk and make it available for reuse.
+ */
+ public void release() {
+ mLength = 0;
+ synchronized (sPool) {
+ // Add the chunk back to the pool as a SoftReference so it can
+ // be gc'd if needed.
+ sPool.offer(new SoftReference<Chunk>(this, sQueue));
+ sPool.notifyAll();
+ }
+ }
+
}
}
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index c74092e..22dca3a 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -56,6 +56,9 @@ public final class CacheManager {
private static long CACHE_THRESHOLD = 6 * 1024 * 1024;
private static long CACHE_TRIM_AMOUNT = 2 * 1024 * 1024;
+ // Limit the maximum cache file size to half of the normal capacity
+ static long CACHE_MAX_SIZE = (CACHE_THRESHOLD - CACHE_TRIM_AMOUNT) / 2;
+
private static boolean mDisabled;
// Reference count the enable/disable transaction
@@ -167,7 +170,7 @@ public final class CacheManager {
* @param context The application context.
*/
static void init(Context context) {
- mDataBase = WebViewDatabase.getInstance(context);
+ mDataBase = WebViewDatabase.getInstance(context.getApplicationContext());
mBaseDir = new File(context.getCacheDir(), "webviewCache");
if (createCacheDirectory() && mClearCacheOnInit) {
removeAllCacheFiles();
@@ -288,16 +291,24 @@ public final class CacheManager {
// only called from WebCore thread
public static CacheResult getCacheFile(String url,
Map<String, String> headers) {
+ return getCacheFile(url, 0, headers);
+ }
+
+ // only called from WebCore thread
+ static CacheResult getCacheFile(String url, long postIdentifier,
+ Map<String, String> headers) {
if (mDisabled) {
return null;
}
- CacheResult result = mDataBase.getCache(url);
+ String databaseKey = getDatabaseKey(url, postIdentifier);
+
+ CacheResult result = mDataBase.getCache(databaseKey);
if (result != null) {
if (result.contentLength == 0) {
if (!checkCacheRedirect(result.httpStatusCode)) {
// this should not happen. If it does, remove it.
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
return null;
}
} else {
@@ -309,7 +320,7 @@ public final class CacheManager {
} catch (FileNotFoundException e) {
// the files in the cache directory can be removed by the
// system. If it is gone, clean up the database
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
return null;
}
}
@@ -357,14 +368,24 @@ public final class CacheManager {
// only called from WebCore thread
public static CacheResult createCacheFile(String url, int statusCode,
Headers headers, String mimeType, boolean forceCache) {
+ return createCacheFile(url, statusCode, headers, mimeType, 0,
+ forceCache);
+ }
+
+ // only called from WebCore thread
+ static CacheResult createCacheFile(String url, int statusCode,
+ Headers headers, String mimeType, long postIdentifier,
+ boolean forceCache) {
if (!forceCache && mDisabled) {
return null;
}
+ String databaseKey = getDatabaseKey(url, postIdentifier);
+
// according to the rfc 2616, the 303 response MUST NOT be cached.
if (statusCode == 303) {
// remove the saved cache if there is any
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
return null;
}
@@ -372,7 +393,7 @@ public final class CacheManager {
// header.
if (checkCacheRedirect(statusCode) && !headers.getSetCookie().isEmpty()) {
// remove the saved cache if there is any
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
return null;
}
@@ -380,9 +401,9 @@ public final class CacheManager {
if (ret == null) {
// this should only happen if the headers has "no-store" in the
// cache-control. remove the saved cache if there is any
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
} else {
- setupFiles(url, ret);
+ setupFiles(databaseKey, ret);
try {
ret.outStream = new FileOutputStream(ret.outFile);
} catch (FileNotFoundException e) {
@@ -413,6 +434,12 @@ public final class CacheManager {
*/
// only called from WebCore thread
public static void saveCacheFile(String url, CacheResult cacheRet) {
+ saveCacheFile(url, 0, cacheRet);
+ }
+
+ // only called from WebCore thread
+ static void saveCacheFile(String url, long postIdentifier,
+ CacheResult cacheRet) {
try {
cacheRet.outStream.close();
} catch (IOException e) {
@@ -424,7 +451,6 @@ public final class CacheManager {
return;
}
- cacheRet.contentLength = cacheRet.outFile.length();
boolean redirect = checkCacheRedirect(cacheRet.httpStatusCode);
if (redirect) {
// location is in database, no need to keep the file
@@ -439,13 +465,22 @@ public final class CacheManager {
return;
}
- mDataBase.addCache(url, cacheRet);
+ mDataBase.addCache(getDatabaseKey(url, postIdentifier), cacheRet);
if (DebugFlags.CACHE_MANAGER) {
Log.v(LOGTAG, "saveCacheFile for url " + url);
}
}
+ static boolean cleanupCacheFile(CacheResult cacheRet) {
+ try {
+ cacheRet.outStream.close();
+ } catch (IOException e) {
+ return false;
+ }
+ return cacheRet.outFile.delete();
+ }
+
/**
* remove all cache files
*
@@ -518,6 +553,11 @@ public final class CacheManager {
}
}
+ private static String getDatabaseKey(String url, long postIdentifier) {
+ if (postIdentifier == 0) return url;
+ return postIdentifier + url;
+ }
+
@SuppressWarnings("deprecation")
private static void setupFiles(String url, CacheResult cacheRet) {
if (true) {
@@ -615,6 +655,9 @@ public final class CacheManager {
private static CacheResult parseHeaders(int statusCode, Headers headers,
String mimeType) {
+ // if the contentLength is already larger than CACHE_MAX_SIZE, skip it
+ if (headers.getContentLength() > CACHE_MAX_SIZE) return null;
+
// TODO: if authenticated or secure, return null
CacheResult ret = new CacheResult();
ret.httpStatusCode = statusCode;
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index e9afcb6..6790c5d 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -90,7 +90,6 @@ class CallbackProxy extends Handler {
private static final int JS_PROMPT = 114;
private static final int JS_UNLOAD = 115;
private static final int ASYNC_KEYEVENTS = 116;
- private static final int TOO_MANY_REDIRECTS = 117;
private static final int DOWNLOAD_FILE = 118;
private static final int REPORT_ERROR = 119;
private static final int RESEND_POST_DATA = 120;
@@ -107,6 +106,7 @@ class CallbackProxy extends Handler {
private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
private static final int RECEIVED_TOUCH_ICON_URL = 132;
private static final int GET_VISITED_HISTORY = 133;
+ private static final int OPEN_FILE_CHOOSER = 134;
// Message triggered by the client to resume execution
private static final int NOTIFY = 200;
@@ -149,6 +149,16 @@ class CallbackProxy extends Handler {
}
/**
+ * Get the WebViewClient.
+ * @return the current WebViewClient instance.
+ *
+ *@hide pending API council approval.
+ */
+ public WebViewClient getWebViewClient() {
+ return mWebViewClient;
+ }
+
+ /**
* Set the WebChromeClient.
* @param client An implementation of WebChromeClient.
*/
@@ -238,8 +248,10 @@ class CallbackProxy extends Handler {
break;
case PAGE_FINISHED:
+ String finishedUrl = (String) msg.obj;
+ mWebView.onPageFinished(finishedUrl);
if (mWebViewClient != null) {
- mWebViewClient.onPageFinished(mWebView, (String) msg.obj);
+ mWebViewClient.onPageFinished(mWebView, finishedUrl);
}
break;
@@ -263,19 +275,6 @@ class CallbackProxy extends Handler {
}
break;
- case TOO_MANY_REDIRECTS:
- Message cancelMsg =
- (Message) msg.getData().getParcelable("cancelMsg");
- Message continueMsg =
- (Message) msg.getData().getParcelable("continueMsg");
- if (mWebViewClient != null) {
- mWebViewClient.onTooManyRedirects(mWebView, cancelMsg,
- continueMsg);
- } else {
- cancelMsg.sendToTarget();
- }
- break;
-
case REPORT_ERROR:
if (mWebViewClient != null) {
int reasonCode = msg.arg1;
@@ -660,6 +659,12 @@ class CallbackProxy extends Handler {
mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
}
break;
+
+ case OPEN_FILE_CHOOSER:
+ if (mWebChromeClient != null) {
+ mWebChromeClient.openFileChooser((UploadFile) msg.obj);
+ }
+ break;
}
}
@@ -758,11 +763,6 @@ class CallbackProxy extends Handler {
}
public void onPageFinished(String url) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- return;
- }
// Performance probe
if (PERF_PROBE) {
// un-comment this if PERF_PROBE is true
@@ -776,19 +776,10 @@ class CallbackProxy extends Handler {
sendMessage(msg);
}
+ // Because this method is public and because CallbackProxy is mistakenly
+ // party of the public classes, we cannot remove this method.
public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- cancelMsg.sendToTarget();
- return;
- }
-
- Message msg = obtainMessage(TOO_MANY_REDIRECTS);
- Bundle bundle = msg.getData();
- bundle.putParcelable("cancelMsg", cancelMsg);
- bundle.putParcelable("continueMsg", continueMsg);
- sendMessage(msg);
+ // deprecated.
}
public void onReceivedError(int errorCode, String description,
@@ -1000,10 +991,10 @@ class CallbackProxy extends Handler {
public void onProgressChanged(int newProgress) {
// Synchronize so that mLatestProgress is up-to-date.
synchronized (this) {
- mLatestProgress = newProgress;
- if (mWebChromeClient == null) {
+ if (mWebChromeClient == null || mLatestProgress == newProgress) {
return;
}
+ mLatestProgress = newProgress;
if (!mProgressUpdatePending) {
sendEmptyMessage(PROGRESS);
mProgressUpdatePending = true;
@@ -1335,4 +1326,40 @@ class CallbackProxy extends Handler {
msg.obj = callback;
sendMessage(msg);
}
+
+ private class UploadFile implements ValueCallback<Uri> {
+ private Uri mValue;
+ public void onReceiveValue(Uri value) {
+ mValue = value;
+ synchronized (CallbackProxy.this) {
+ CallbackProxy.this.notify();
+ }
+ }
+ public Uri getResult() {
+ return mValue;
+ }
+ }
+
+ /**
+ * Called by WebViewCore to open a file chooser.
+ */
+ /* package */ Uri openFileChooser() {
+ if (mWebChromeClient == null) {
+ return null;
+ }
+ Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
+ UploadFile uploadFile = new UploadFile();
+ myMessage.obj = uploadFile;
+ synchronized (this) {
+ sendMessage(myMessage);
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(LOGTAG,
+ "Caught exception while waiting for openFileChooser");
+ Log.e(LOGTAG, Log.getStackTraceString(e));
+ }
+ }
+ return uploadFile.getResult();
+ }
}
diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java
index 14375d2..abe9178 100644
--- a/core/java/android/webkit/CookieSyncManager.java
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -93,7 +93,7 @@ public final class CookieSyncManager extends WebSyncManager {
public static synchronized CookieSyncManager createInstance(
Context context) {
if (sRef == null) {
- sRef = new CookieSyncManager(context);
+ sRef = new CookieSyncManager(context.getApplicationContext());
}
return sRef;
}
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index c46702e..16feaa9 100644
--- a/core/java/android/webkit/DateSorter.java
+++ b/core/java/android/webkit/DateSorter.java
@@ -38,7 +38,7 @@ public class DateSorter {
/** must be >= 3 */
public static final int DAY_COUNT = 5;
- private long [] mBins = new long[DAY_COUNT];
+ private long [] mBins = new long[DAY_COUNT-1];
private String [] mLabels = new String[DAY_COUNT];
private static final int NUM_DAYS_AGO = 5;
@@ -54,15 +54,13 @@ public class DateSorter {
// Create the bins
mBins[0] = c.getTimeInMillis(); // Today
- c.roll(Calendar.DAY_OF_YEAR, -1);
+ c.add(Calendar.DAY_OF_YEAR, -1);
mBins[1] = c.getTimeInMillis(); // Yesterday
- c.roll(Calendar.DAY_OF_YEAR, -(NUM_DAYS_AGO - 1));
+ c.add(Calendar.DAY_OF_YEAR, -(NUM_DAYS_AGO - 1));
mBins[2] = c.getTimeInMillis(); // Five days ago
- c.roll(Calendar.DAY_OF_YEAR, NUM_DAYS_AGO); // move back to today
- c.roll(Calendar.MONTH, -1);
+ c.add(Calendar.DAY_OF_YEAR, NUM_DAYS_AGO); // move back to today
+ c.add(Calendar.MONTH, -1);
mBins[3] = c.getTimeInMillis(); // One month ago
- c.roll(Calendar.MONTH, -1);
- mBins[4] = c.getTimeInMillis(); // Over one month ago
// build labels
mLabels[0] = context.getText(com.android.internal.R.string.today).toString();
@@ -84,11 +82,11 @@ public class DateSorter {
* date bin this date belongs to
*/
public int getIndex(long time) {
- // Lame linear search
- for (int i = 0; i < DAY_COUNT; i++) {
+ int lastDay = DAY_COUNT - 1;
+ for (int i = 0; i < lastDay; i++) {
if (time > mBins[i]) return i;
}
- return DAY_COUNT - 1;
+ return lastDay;
}
/**
@@ -96,6 +94,7 @@ public class DateSorter {
* @return string label suitable for display to user
*/
public String getLabel(int index) {
+ if (index < 0 || index >= DAY_COUNT) return "";
return mLabels[index];
}
@@ -105,17 +104,22 @@ public class DateSorter {
* @return date boundary at given index
*/
public long getBoundary(int index) {
+ int lastDay = DAY_COUNT - 1;
+ // Error case
+ if (index < 0 || index > lastDay) index = 0;
+ // Since this provides a lower boundary on dates that will be included
+ // in the given bin, provide the smallest value
+ if (index == lastDay) return Long.MIN_VALUE;
return mBins[index];
}
/**
* Calcuate 12:00am by zeroing out hour, minute, second, millisecond
*/
- private Calendar beginningOfDay(Calendar c) {
+ private void beginningOfDay(Calendar c) {
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
- return c;
}
}
diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java
index 8e25395..dca52f6 100644
--- a/core/java/android/webkit/DebugFlags.java
+++ b/core/java/android/webkit/DebugFlags.java
@@ -32,6 +32,8 @@ class DebugFlags {
public static final boolean CALLBACK_PROXY = false;
public static final boolean COOKIE_MANAGER = false;
public static final boolean COOKIE_SYNC_MANAGER = false;
+ public static final boolean DRAG_TRACKER = false;
+ public static final String DRAG_TRACKER_LOGTAG = "skia";
public static final boolean FRAME_LOADER = false;
public static final boolean J_WEB_CORE_JAVA_BRIDGE = false;// HIGHLY VERBOSE
public static final boolean LOAD_LISTENER = false;
diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java
index 085f1f4..974ccbf 100644
--- a/core/java/android/webkit/FileLoader.java
+++ b/core/java/android/webkit/FileLoader.java
@@ -23,9 +23,12 @@ import android.content.res.AssetManager;
import android.net.http.EventHandler;
import android.net.http.Headers;
import android.os.Environment;
+import android.util.Log;
+import android.util.TypedValue;
import java.io.File;
import java.io.FileInputStream;
+import java.lang.reflect.Field;
/**
* This class is a concrete implementation of StreamLoader that uses a
@@ -35,10 +38,19 @@ import java.io.FileInputStream;
class FileLoader extends StreamLoader {
private String mPath; // Full path to the file to load
- private Context mContext; // Application context, used for asset loads
- private boolean mIsAsset; // Indicates if the load is an asset or not
+ private Context mContext; // Application context, used for asset/res loads
+ private int mType; // Indicates the type of the load
private boolean mAllowFileAccess; // Allow/block file system access
+ // used for files under asset directory
+ static final int TYPE_ASSET = 1;
+ // used for files under res directory
+ static final int TYPE_RES = 2;
+ // generic file
+ static final int TYPE_FILE = 3;
+
+ private static final String LOGTAG = "webkit";
+
/**
* Construct a FileLoader with the file URL specified as the content
* source.
@@ -51,19 +63,24 @@ class FileLoader extends StreamLoader {
* on the file system.
*/
FileLoader(String url, LoadListener loadListener, Context context,
- boolean asset, boolean allowFileAccess) {
+ int type, boolean allowFileAccess) {
super(loadListener);
- mIsAsset = asset;
+ mType = type;
mContext = context;
mAllowFileAccess = allowFileAccess;
// clean the Url
int index = url.indexOf('?');
- if (mIsAsset) {
+ if (mType == TYPE_ASSET) {
mPath = index > 0 ? URLUtil.stripAnchor(
url.substring(URLUtil.ASSET_BASE.length(), index)) :
URLUtil.stripAnchor(url.substring(
URLUtil.ASSET_BASE.length()));
+ } else if (mType == TYPE_RES) {
+ mPath = index > 0 ? URLUtil.stripAnchor(
+ url.substring(URLUtil.RESOURCE_BASE.length(), index)) :
+ URLUtil.stripAnchor(url.substring(
+ URLUtil.RESOURCE_BASE.length()));
} else {
mPath = index > 0 ? URLUtil.stripAnchor(
url.substring(URLUtil.FILE_BASE.length(), index)) :
@@ -84,13 +101,69 @@ class FileLoader extends StreamLoader {
@Override
protected boolean setupStreamAndSendStatus() {
try {
- if (mIsAsset) {
+ if (mType == TYPE_ASSET) {
try {
mDataStream = mContext.getAssets().open(mPath);
} catch (java.io.FileNotFoundException ex) {
// try the rest files included in the package
mDataStream = mContext.getAssets().openNonAsset(mPath);
}
+ } else if (mType == TYPE_RES) {
+ // get the resource id from the path. e.g. for the path like
+ // drawable/foo.png, the id is located at field "foo" of class
+ // "<package>.R$drawable"
+ if (mPath == null || mPath.length() == 0) {
+ Log.e(LOGTAG, "Need a path to resolve the res file");
+ mHandler.error(EventHandler.FILE_ERROR, mContext
+ .getString(R.string.httpErrorFileNotFound));
+ return false;
+
+ }
+ int slash = mPath.indexOf('/');
+ int dot = mPath.indexOf('.', slash);
+ if (slash == -1 || dot == -1) {
+ Log.e(LOGTAG, "Incorrect res path: " + mPath);
+ mHandler.error(EventHandler.FILE_ERROR, mContext
+ .getString(R.string.httpErrorFileNotFound));
+ return false;
+ }
+ String subClassName = mPath.substring(0, slash);
+ String fieldName = mPath.substring(slash + 1, dot);
+ String errorMsg = null;
+ try {
+ final Class<?> d = mContext.getApplicationContext()
+ .getClassLoader().loadClass(
+ mContext.getPackageName() + ".R$"
+ + subClassName);
+ final Field field = d.getField(fieldName);
+ final int id = field.getInt(null);
+ TypedValue value = new TypedValue();
+ mContext.getResources().getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_STRING) {
+ mDataStream = mContext.getAssets().openNonAsset(
+ value.assetCookie, value.string.toString(),
+ AssetManager.ACCESS_STREAMING);
+ } else {
+ errorMsg = "Only support TYPE_STRING for the res files";
+ }
+ } catch (ClassNotFoundException e) {
+ errorMsg = "Can't find class: "
+ + mContext.getPackageName() + ".R$" + subClassName;
+ } catch (SecurityException e) {
+ errorMsg = "Caught SecurityException: " + e;
+ } catch (NoSuchFieldException e) {
+ errorMsg = "Can't find field: " + fieldName + " in "
+ + mContext.getPackageName() + ".R$" + subClassName;
+ } catch (IllegalArgumentException e) {
+ errorMsg = "Caught IllegalArgumentException: " + e;
+ } catch (IllegalAccessException e) {
+ errorMsg = "Caught IllegalAccessException: " + e;
+ }
+ if (errorMsg != null) {
+ mHandler.error(EventHandler.FILE_ERROR, mContext
+ .getString(R.string.httpErrorFileNotFound));
+ return false;
+ }
} else {
if (!mAllowFileAccess) {
mHandler.error(EventHandler.FILE_ERROR,
@@ -131,8 +204,8 @@ class FileLoader extends StreamLoader {
* file system.
*/
public static void requestUrl(String url, LoadListener loadListener,
- Context context, boolean asset, boolean allowFileAccess) {
- FileLoader loader = new FileLoader(url, loadListener, context, asset,
+ Context context, int type, boolean allowFileAccess) {
+ FileLoader loader = new FileLoader(url, loadListener, context, type,
allowFileAccess);
loader.load();
}
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index c1eeb3b..51f60c3 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -142,11 +142,15 @@ class FrameLoader {
}
if (URLUtil.isAssetUrl(url)) {
FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
- true, settings.getAllowFileAccess());
+ FileLoader.TYPE_ASSET, true);
+ return true;
+ } else if (URLUtil.isResourceUrl(url)) {
+ FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
+ FileLoader.TYPE_RES, true);
return true;
} else if (URLUtil.isFileUrl(url)) {
FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
- false, settings.getAllowFileAccess());
+ FileLoader.TYPE_FILE, settings.getAllowFileAccess());
return true;
} else if (URLUtil.isContentUrl(url)) {
// Send the raw url to the ContentLoader because it will do a
@@ -242,7 +246,7 @@ class FrameLoader {
// to load POST content in a history navigation.
case WebSettings.LOAD_CACHE_ONLY: {
CacheResult result = CacheManager.getCacheFile(mListener.url(),
- null);
+ mListener.postIdentifier(), null);
if (result != null) {
startCacheLoad(result);
} else {
@@ -270,7 +274,7 @@ class FrameLoader {
// Get the cache file name for the current URL, passing null for
// the validation headers causes no validation to occur
CacheResult result = CacheManager.getCacheFile(mListener.url(),
- null);
+ mListener.postIdentifier(), null);
if (result != null) {
startCacheLoad(result);
return true;
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index b7a9065..429b335 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -48,6 +48,8 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
/**
* <p>Proxy for HTML5 video views.
@@ -71,6 +73,9 @@ class HTML5VideoViewProxy extends Handler
private static final int ENDED = 201;
private static final int POSTER_FETCHED = 202;
+ // Timer thread -> UI thread
+ private static final int TIMEUPDATE = 300;
+
// The C++ MediaPlayerPrivateAndroid object.
int mNativePointer;
// The handler for WebCore thread messages;
@@ -95,6 +100,22 @@ class HTML5VideoViewProxy extends Handler
private static View mProgressView;
// The container for the progress view and video view
private static FrameLayout mLayout;
+ // The timer for timeupate events.
+ // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
+ private static Timer mTimer;
+ private static final class TimeupdateTask extends TimerTask {
+ private HTML5VideoViewProxy mProxy;
+
+ public TimeupdateTask(HTML5VideoViewProxy proxy) {
+ mProxy = proxy;
+ }
+
+ public void run() {
+ mProxy.onTimeupdate();
+ }
+ }
+ // The spec says the timer should fire every 250 ms or less.
+ private static final int TIMEUPDATE_PERIOD = 250; // ms
private static final WebChromeClient.CustomViewCallback mCallback =
new WebChromeClient.CustomViewCallback() {
@@ -104,6 +125,8 @@ class HTML5VideoViewProxy extends Handler
// which happens when the video view is detached from its parent
// view. This happens in the WebChromeClient before this method
// is invoked.
+ mTimer.cancel();
+ mTimer = null;
mCurrentProxy.playbackEnded();
mCurrentProxy = null;
mLayout.removeView(mVideoView);
@@ -118,11 +141,19 @@ class HTML5VideoViewProxy extends Handler
public static void play(String url, int time, HTML5VideoViewProxy proxy,
WebChromeClient client) {
+ if (mCurrentProxy == proxy) {
+ if (!mVideoView.isPlaying()) {
+ mVideoView.start();
+ }
+ return;
+ }
+
if (mCurrentProxy != null) {
// Some other video is already playing. Notify the caller that its playback ended.
proxy.playbackEnded();
return;
}
+
mCurrentProxy = proxy;
// Create a FrameLayout that will contain the VideoView and the
// progress view (if any).
@@ -146,10 +177,23 @@ class HTML5VideoViewProxy extends Handler
mProgressView.setVisibility(View.VISIBLE);
}
mLayout.setVisibility(View.VISIBLE);
+ mTimer = new Timer();
mVideoView.start();
client.onShowCustomView(mLayout, mCallback);
}
+ public static boolean isPlaying(HTML5VideoViewProxy proxy) {
+ return (mCurrentProxy == proxy && mVideoView != null && mVideoView.isPlaying());
+ }
+
+ public static int getCurrentPosition() {
+ int currentPosMs = 0;
+ if (mVideoView != null) {
+ currentPosMs = mVideoView.getCurrentPosition();
+ }
+ return currentPosMs;
+ }
+
public static void seek(int time, HTML5VideoViewProxy proxy) {
if (mCurrentProxy == proxy && time >= 0 && mVideoView != null) {
mVideoView.seekTo(time);
@@ -159,10 +203,13 @@ class HTML5VideoViewProxy extends Handler
public static void pause(HTML5VideoViewProxy proxy) {
if (mCurrentProxy == proxy && mVideoView != null) {
mVideoView.pause();
+ mTimer.purge();
}
}
public static void onPrepared() {
+ mTimer.schedule(new TimeupdateTask(mCurrentProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
+
if (mProgressView == null || mLayout == null) {
return;
}
@@ -199,9 +246,15 @@ class HTML5VideoViewProxy extends Handler
public void playbackEnded() {
Message msg = Message.obtain(mWebCoreHandler, ENDED);
mWebCoreHandler.sendMessage(msg);
+ // also send a message to ourselves to return to the WebView
+ sendMessage(obtainMessage(ENDED));
}
- // Handler for the messages from WebCore thread to the UI thread.
+ public void onTimeupdate() {
+ sendMessage(obtainMessage(TIMEUPDATE));
+ }
+
+ // Handler for the messages from WebCore or Timer thread to the UI thread.
@Override
public void handleMessage(Message msg) {
// This executes on the UI thread.
@@ -224,6 +277,7 @@ class HTML5VideoViewProxy extends Handler
VideoPlayer.pause(this);
break;
}
+ case ENDED:
case ERROR: {
WebChromeClient client = mWebView.getWebChromeClient();
if (client != null) {
@@ -238,6 +292,12 @@ class HTML5VideoViewProxy extends Handler
}
break;
}
+ case TIMEUPDATE: {
+ if (VideoPlayer.isPlaying(this)) {
+ sendTimeupdate();
+ }
+ break;
+ }
}
}
@@ -407,6 +467,9 @@ class HTML5VideoViewProxy extends Handler
Bitmap poster = (Bitmap) msg.obj;
nativeOnPosterFetched(poster, mNativePointer);
break;
+ case TIMEUPDATE:
+ nativeOnTimeupdate(msg.arg1, mNativePointer);
+ break;
}
}
};
@@ -423,6 +486,12 @@ class HTML5VideoViewProxy extends Handler
mWebCoreHandler.sendMessage(msg);
}
+ private void sendTimeupdate() {
+ Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
+ msg.arg1 = VideoPlayer.getCurrentPosition();
+ mWebCoreHandler.sendMessage(msg);
+ }
+
public Context getContext() {
return mWebView.getContext();
}
@@ -503,4 +572,5 @@ class HTML5VideoViewProxy extends Handler
private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
private native void nativeOnEnded(int nativePointer);
private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
+ private native void nativeOnTimeupdate(int position, int nativePointer);
}
diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/android/webkit/HttpDateTime.java
index 2f46f2b..042953c 100644
--- a/core/java/android/webkit/HttpDateTime.java
+++ b/core/java/android/webkit/HttpDateTime.java
@@ -50,13 +50,15 @@ public final class HttpDateTime {
* Wdy Mon DD HH:MM:SS YYYY GMT
*
* HH can be H if the first digit is zero.
+ *
+ * Mon can be the full name of the month.
*/
private static final String HTTP_DATE_RFC_REGEXP =
- "([0-9]{1,2})[- ]([A-Za-z]{3,3})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
private static final String HTTP_DATE_ANSIC_REGEXP =
- "[ ]([A-Za-z]{3,3})[ ]+([0-9]{1,2})[ ]"
+ "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
/**
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 4c17f99..cdc6608 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -16,9 +16,14 @@
package android.webkit;
+import android.content.ActivityNotFoundException;
import android.content.Context;
-import android.net.WebAddress;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.net.ParseException;
+import android.net.Uri;
+import android.net.WebAddress;
import android.net.http.EventHandler;
import android.net.http.Headers;
import android.net.http.HttpAuthHeader;
@@ -78,7 +83,7 @@ class LoadListener extends Handler implements EventHandler {
private static int sNativeLoaderCount;
- private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192);
+ private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder();
private String mUrl;
private WebAddress mUri;
@@ -104,6 +109,7 @@ class LoadListener extends Handler implements EventHandler {
private SslError mSslError;
private RequestHandle mRequestHandle;
private RequestHandle mSslErrorRequestHandle;
+ private long mPostIdentifier;
// Request data. It is only valid when we are doing a load from the
// cache. It is needed if the cache returns a redirect
@@ -116,6 +122,8 @@ class LoadListener extends Handler implements EventHandler {
// Does this loader correspond to the main-frame top-level page?
private boolean mIsMainPageLoader;
+ private final boolean mIsMainResourceLoader;
+ private final boolean mUserGesture;
private Headers mHeaders;
@@ -123,13 +131,14 @@ class LoadListener extends Handler implements EventHandler {
// Public functions
// =========================================================================
- public static LoadListener getLoadListener(
- Context context, BrowserFrame frame, String url,
- int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
+ public static LoadListener getLoadListener(Context context,
+ BrowserFrame frame, String url, int nativeLoader,
+ boolean synchronous, boolean isMainPageLoader,
+ boolean isMainResource, boolean userGesture, long postIdentifier) {
sNativeLoaderCount += 1;
- return new LoadListener(
- context, frame, url, nativeLoader, synchronous, isMainPageLoader);
+ return new LoadListener(context, frame, url, nativeLoader, synchronous,
+ isMainPageLoader, isMainResource, userGesture, postIdentifier);
}
public static int getNativeLoaderCount() {
@@ -137,7 +146,8 @@ class LoadListener extends Handler implements EventHandler {
}
LoadListener(Context context, BrowserFrame frame, String url,
- int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
+ int nativeLoader, boolean synchronous, boolean isMainPageLoader,
+ boolean isMainResource, boolean userGesture, long postIdentifier) {
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener constructor url=" + url);
}
@@ -150,6 +160,9 @@ class LoadListener extends Handler implements EventHandler {
mMessageQueue = new Vector<Message>();
}
mIsMainPageLoader = isMainPageLoader;
+ mIsMainResourceLoader = isMainResource;
+ mUserGesture = userGesture;
+ mPostIdentifier = postIdentifier;
}
/**
@@ -291,6 +304,13 @@ class LoadListener extends Handler implements EventHandler {
sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers));
}
+ // This is the same regex that DOMImplementation uses to check for xml
+ // content. Use this to check if another Activity wants to handle the
+ // content before giving it to webkit.
+ private static final String XML_MIME_TYPE =
+ "^[\\w_\\-+~!$\\^{}|.%'`#&*]+/" +
+ "[\\w_\\-+~!$\\^{}|.%'`#&*]+\\+xml$";
+
// Does the header parsing work on the WebCore thread.
private void handleHeaders(Headers headers) {
if (mCancelled) return;
@@ -349,6 +369,26 @@ class LoadListener extends Handler implements EventHandler {
than the headers that are returned from the server. */
guessMimeType();
}
+ // At this point, mMimeType has been set to non-null.
+ if (mIsMainPageLoader && mIsMainResourceLoader && mUserGesture &&
+ Pattern.matches(XML_MIME_TYPE, mMimeType)) {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setDataAndType(Uri.parse(url()), mMimeType);
+ ResolveInfo info = mContext.getPackageManager().resolveActivity(i,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (info != null && !mContext.getPackageName().equals(
+ info.activityInfo.packageName)) {
+ // someone (other than the current app) knows how to
+ // handle this mime type.
+ try {
+ mContext.startActivity(i);
+ mBrowserFrame.stopLoading();
+ return;
+ } catch (ActivityNotFoundException ex) {
+ // continue loading internally.
+ }
+ }
+ }
// is it an authentication request?
boolean mustAuthenticate = (mStatusCode == HTTP_AUTH ||
@@ -408,9 +448,14 @@ class LoadListener extends Handler implements EventHandler {
mStatusCode == HTTP_MOVED_PERMANENTLY ||
mStatusCode == HTTP_TEMPORARY_REDIRECT) &&
mNativeLoader != 0) {
- if (!mFromCache && mRequestHandle != null) {
+ // for POST request, only cache the result if there is an identifier
+ // associated with it. postUrl() or form submission should set the
+ // identifier while XHR POST doesn't.
+ if (!mFromCache && mRequestHandle != null
+ && (!mRequestHandle.getMethod().equals("POST")
+ || mPostIdentifier != 0)) {
mCacheResult = CacheManager.createCacheFile(mUrl, mStatusCode,
- headers, mMimeType, false);
+ headers, mMimeType, mPostIdentifier, false);
}
if (mCacheResult != null) {
mCacheResult.encoding = mEncoding;
@@ -522,17 +567,18 @@ class LoadListener extends Handler implements EventHandler {
* IMPORTANT: as this is called from network thread, can't call native
* directly
* XXX: Unlike the other network thread methods, this method can do the
- * work of decoding the data and appending it to the data builder because
- * mDataBuilder is a thread-safe structure.
+ * work of decoding the data and appending it to the data builder.
*/
public void data(byte[] data, int length) {
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener.data(): url: " + url());
}
- // Synchronize on mData because commitLoad may write mData to WebCore
- // and we don't want to replace mData or mDataLength at the same time
- // as a write.
+ // The reason isEmpty() and append() need to synchronized together is
+ // because it is possible for getFirstChunk() to be called multiple
+ // times between isEmpty() and append(). This could cause commitLoad()
+ // to finish before processing the newly appended data and no message
+ // will be sent.
boolean sendMessage = false;
synchronized (mDataBuilder) {
sendMessage = mDataBuilder.isEmpty();
@@ -636,7 +682,7 @@ class LoadListener extends Handler implements EventHandler {
*/
boolean checkCache(Map<String, String> headers) {
// Get the cache file name for the current URL
- CacheResult result = CacheManager.getCacheFile(url(),
+ CacheResult result = CacheManager.getCacheFile(url(), mPostIdentifier,
headers);
// Go ahead and set the cache loader to null in case the result is
@@ -861,6 +907,10 @@ class LoadListener extends Handler implements EventHandler {
}
}
+ long postIdentifier() {
+ return mPostIdentifier;
+ }
+
void attachRequestHandle(RequestHandle requestHandle) {
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " +
@@ -886,8 +936,11 @@ class LoadListener extends Handler implements EventHandler {
void downloadFile() {
// Setting the Cache Result to null ensures that this
// content is not added to the cache
- mCacheResult = null;
-
+ if (mCacheResult != null) {
+ CacheManager.cleanupCacheFile(mCacheResult);
+ mCacheResult = null;
+ }
+
// Inform the client that they should download a file
mBrowserFrame.getCallbackProxy().onDownloadStart(url(),
mBrowserFrame.getUserAgentString(),
@@ -907,8 +960,9 @@ class LoadListener extends Handler implements EventHandler {
* be used. This is just for forward/back navigation to a POST
* URL.
*/
- static boolean willLoadFromCache(String url) {
- boolean inCache = CacheManager.getCacheFile(url, null) != null;
+ static boolean willLoadFromCache(String url, long identifier) {
+ boolean inCache =
+ CacheManager.getCacheFile(url, identifier, null) != null;
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " +
inCache);
@@ -1009,28 +1063,34 @@ class LoadListener extends Handler implements EventHandler {
if (mIsMainPageLoader) {
String type = sCertificateTypeMap.get(mMimeType);
if (type != null) {
- // In the case of downloading certificate, we will save it to
- // the KeyStore and stop the current loading so that it will not
- // generate a new history page
- byte[] cert = new byte[mDataBuilder.getByteSize()];
- int offset = 0;
- while (true) {
- ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
- if (c == null) break;
-
- if (c.mLength != 0) {
- System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
- offset += c.mLength;
+ // This must be synchronized so that no more data can be added
+ // after getByteSize returns.
+ synchronized (mDataBuilder) {
+ // In the case of downloading certificate, we will save it
+ // to the KeyStore and stop the current loading so that it
+ // will not generate a new history page
+ byte[] cert = new byte[mDataBuilder.getByteSize()];
+ int offset = 0;
+ while (true) {
+ ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
+ if (c == null) break;
+
+ if (c.mLength != 0) {
+ System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
+ offset += c.mLength;
+ }
+ c.release();
}
- mDataBuilder.releaseChunk(c);
+ CertTool.addCertificate(mContext, type, cert);
+ mBrowserFrame.stopLoading();
+ return;
}
- CertTool.addCertificate(mContext, type, cert);
- mBrowserFrame.stopLoading();
- return;
}
}
- // Give the data to WebKit now
+ // Give the data to WebKit now. We don't have to synchronize on
+ // mDataBuilder here because pulling each chunk removes it from the
+ // internal list so it cannot be modified.
PerfChecker checker = new PerfChecker();
ByteArrayBuilder.Chunk c;
while (true) {
@@ -1039,15 +1099,23 @@ class LoadListener extends Handler implements EventHandler {
if (c.mLength != 0) {
if (mCacheResult != null) {
- try {
- mCacheResult.outStream.write(c.mArray, 0, c.mLength);
- } catch (IOException e) {
+ mCacheResult.contentLength += c.mLength;
+ if (mCacheResult.contentLength > CacheManager.CACHE_MAX_SIZE) {
+ CacheManager.cleanupCacheFile(mCacheResult);
mCacheResult = null;
+ } else {
+ try {
+ mCacheResult.outStream
+ .write(c.mArray, 0, c.mLength);
+ } catch (IOException e) {
+ CacheManager.cleanupCacheFile(mCacheResult);
+ mCacheResult = null;
+ }
}
}
nativeAddData(c.mArray, c.mLength);
}
- mDataBuilder.releaseChunk(c);
+ c.release();
checker.responseAlert("res nativeAddData");
}
}
@@ -1059,7 +1127,9 @@ class LoadListener extends Handler implements EventHandler {
void tearDown() {
if (mCacheResult != null) {
if (getErrorID() == OK) {
- CacheManager.saveCacheFile(mUrl, mCacheResult);
+ CacheManager.saveCacheFile(mUrl, mPostIdentifier, mCacheResult);
+ } else {
+ CacheManager.cleanupCacheFile(mCacheResult);
}
// we need to reset mCacheResult to be null
@@ -1124,7 +1194,10 @@ class LoadListener extends Handler implements EventHandler {
mRequestHandle = null;
}
- mCacheResult = null;
+ if (mCacheResult != null) {
+ CacheManager.cleanupCacheFile(mCacheResult);
+ mCacheResult = null;
+ }
mCancelled = true;
clearNativeLoader();
@@ -1187,7 +1260,10 @@ class LoadListener extends Handler implements EventHandler {
// Cache the redirect response
if (mCacheResult != null) {
if (getErrorID() == OK) {
- CacheManager.saveCacheFile(mUrl, mCacheResult);
+ CacheManager.saveCacheFile(mUrl, mPostIdentifier,
+ mCacheResult);
+ } else {
+ CacheManager.cleanupCacheFile(mCacheResult);
}
mCacheResult = null;
}
@@ -1210,8 +1286,17 @@ class LoadListener extends Handler implements EventHandler {
// mRequestHandle can be null when the request was satisfied
// by the cache, and the cache returned a redirect
if (mRequestHandle != null) {
- mRequestHandle.setupRedirect(mUrl, mStatusCode,
- mRequestHeaders);
+ try {
+ mRequestHandle.setupRedirect(mUrl, mStatusCode,
+ mRequestHeaders);
+ } catch(RuntimeException e) {
+ Log.e(LOGTAG, e.getMessage());
+ // Signal a bad url error if we could not load the
+ // redirection.
+ handleError(EventHandler.ERROR_BAD_URL,
+ mContext.getString(R.string.httpErrorBadUrl));
+ return;
+ }
} else {
// If the original request came from the cache, there is no
// RequestHandle, we have to create a new one through
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index fffba1b..84a8a3c 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -431,6 +431,8 @@ public class MimeTypeMap {
sMimeTypeMap.loadEntry("text/calendar", "icz");
sMimeTypeMap.loadEntry("text/comma-separated-values", "csv");
sMimeTypeMap.loadEntry("text/css", "css");
+ sMimeTypeMap.loadEntry("text/html", "htm");
+ sMimeTypeMap.loadEntry("text/html", "html");
sMimeTypeMap.loadEntry("text/h323", "323");
sMimeTypeMap.loadEntry("text/iuls", "uls");
sMimeTypeMap.loadEntry("text/mathml", "mml");
@@ -481,6 +483,7 @@ public class MimeTypeMap {
sMimeTypeMap.loadEntry("video/dv", "dif");
sMimeTypeMap.loadEntry("video/dv", "dv");
sMimeTypeMap.loadEntry("video/fli", "fli");
+ sMimeTypeMap.loadEntry("video/m4v", "m4v");
sMimeTypeMap.loadEntry("video/mpeg", "mpeg");
sMimeTypeMap.loadEntry("video/mpeg", "mpg");
sMimeTypeMap.loadEntry("video/mpeg", "mpe");
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
index af0cb1e..598f20d 100644
--- a/core/java/android/webkit/Network.java
+++ b/core/java/android/webkit/Network.java
@@ -163,10 +163,10 @@ class Network {
return false;
}
- // asset, file system or data stream are handled in the other code path.
- // This only handles network request.
- if (URLUtil.isAssetUrl(url) || URLUtil.isFileUrl(url) ||
- URLUtil.isDataUrl(url)) {
+ // asset, res, file system or data stream are handled in the other code
+ // path. This only handles network request.
+ if (URLUtil.isAssetUrl(url) || URLUtil.isResourceUrl(url)
+ || URLUtil.isFileUrl(url) || URLUtil.isDataUrl(url)) {
return false;
}
@@ -180,20 +180,24 @@ class Network {
}
RequestQueue q = mRequestQueue;
+ RequestHandle handle = null;
if (loader.isSynchronous()) {
- q = new RequestQueue(loader.getContext(), 1);
- }
-
- RequestHandle handle = q.queueRequest(
- url, loader.getWebAddress(), method, headers, loader,
- bodyProvider, bodyLength);
- loader.attachRequestHandle(handle);
-
- if (loader.isSynchronous()) {
- handle.waitUntilComplete();
+ handle = q.queueSynchronousRequest(url, loader.getWebAddress(),
+ method, headers, loader, bodyProvider, bodyLength);
+ loader.attachRequestHandle(handle);
+ handle.processRequest();
loader.loadSynchronousMessages();
- q.shutdown();
+ } else {
+ handle = q.queueRequest(url, loader.getWebAddress(), method,
+ headers, loader, bodyProvider, bodyLength);
+ // FIXME: Although this is probably a rare condition, normal network
+ // requests are processed in a separate thread. This means that it
+ // is possible to process part of the request before setting the
+ // request handle on the loader. We should probably refactor this to
+ // ensure the handle is attached before processing begins.
+ loader.attachRequestHandle(handle);
}
+
return true;
}
diff --git a/core/java/android/webkit/PluginActivity.java b/core/java/android/webkit/PluginActivity.java
deleted file mode 100644
index cda7b59..0000000
--- a/core/java/android/webkit/PluginActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 android.webkit;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-
-/**
- * This activity is invoked when a plugin elects to go into full screen mode.
- * @hide
- */
-public class PluginActivity extends Activity {
-
- /* package */ static final String INTENT_EXTRA_PACKAGE_NAME =
- "android.webkit.plugin.PACKAGE_NAME";
- /* package */ static final String INTENT_EXTRA_CLASS_NAME =
- "android.webkit.plugin.CLASS_NAME";
- /* package */ static final String INTENT_EXTRA_NPP_INSTANCE =
- "android.webkit.plugin.NPP_INSTANCE";
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Intent intent = getIntent();
- if (intent == null) {
- // No intent means no class to lookup.
- finish();
- }
- final String packageName =
- intent.getStringExtra(INTENT_EXTRA_PACKAGE_NAME);
- final String className = intent.getStringExtra(INTENT_EXTRA_CLASS_NAME);
- final int npp = intent.getIntExtra(INTENT_EXTRA_NPP_INSTANCE, -1);
- // Retrieve the PluginStub implemented in packageName.className
- PluginStub stub =
- PluginUtil.getPluginStub(this, packageName, className);
-
- if (stub != null) {
- View pluginView = stub.getFullScreenView(npp, this);
- if (pluginView != null) {
- setContentView(pluginView);
- } else {
- // No custom full-sreen view returned by the plugin, odd but
- // just in case, finish the activity.
- finish();
- }
- } else {
- finish();
- }
- }
-}
diff --git a/core/java/android/webkit/PluginFullScreenHolder.java b/core/java/android/webkit/PluginFullScreenHolder.java
new file mode 100644
index 0000000..b641803
--- /dev/null
+++ b/core/java/android/webkit/PluginFullScreenHolder.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2009, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package android.webkit;
+
+import android.app.Dialog;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+class PluginFullScreenHolder extends Dialog {
+
+ private static final String LOGTAG = "FullScreenHolder";
+
+ private final WebView mWebView;
+ private final int mNpp;
+ private View mContentView;
+ private int mX;
+ private int mY;
+ private int mWidth;
+ private int mHeight;
+
+ PluginFullScreenHolder(WebView webView, int npp) {
+ super(webView.getContext(), android.R.style.Theme_NoTitleBar_Fullscreen);
+ mWebView = webView;
+ mNpp = npp;
+ }
+
+ Rect getBound() {
+ return new Rect(mX, mY, mWidth, mHeight);
+ }
+
+ /*
+ * x, y, width, height are in the caller's view coordinate system. (x, y) is
+ * relative to the top left corner of the caller's view.
+ */
+ void updateBound(int x, int y, int width, int height) {
+ mX = x;
+ mY = y;
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public void setContentView(View contentView) {
+ super.setContentView(contentView);
+ mContentView = contentView;
+ }
+
+ @Override
+ public void onBackPressed() {
+ mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN)
+ .sendToTarget();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (event.isSystem()) {
+ return super.onKeyDown(keyCode, event);
+ }
+ mWebView.onKeyDown(keyCode, event);
+ // always return true as we are the handler
+ return true;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (event.isSystem()) {
+ return super.onKeyUp(keyCode, event);
+ }
+ mWebView.onKeyUp(keyCode, event);
+ // always return true as we are the handler
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final float x = event.getX();
+ final float y = event.getY();
+ // TODO: find a way to know when the dialog size changed so that we can
+ // cache the ratio
+ final View decorView = getWindow().getDecorView();
+ event.setLocation(mX + x * mWidth / decorView.getWidth(),
+ mY + y * mHeight / decorView.getHeight());
+ mWebView.onTouchEvent(event);
+ // always return true as we are the handler
+ return true;
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ mWebView.onTrackballEvent(event);
+ // always return true as we are the handler
+ return true;
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ // manually remove the contentView's parent since the dialog does not
+ if (mContentView != null && mContentView.getParent() != null) {
+ ViewGroup vg = (ViewGroup) mContentView.getParent();
+ vg.removeView(mContentView);
+ }
+ mWebView.getWebViewCore().sendMessage(
+ WebViewCore.EventHub.HIDE_FULLSCREEN, mNpp, 0);
+ }
+
+}
diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java
index 4588f46..cdcb662 100644
--- a/core/java/android/webkit/PluginManager.java
+++ b/core/java/android/webkit/PluginManager.java
@@ -21,6 +21,7 @@ import java.util.List;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
@@ -60,6 +61,9 @@ public class PluginManager {
private static final String LOGTAG = "webkit";
+ private static final String PLUGIN_TYPE = "type";
+ private static final String TYPE_NATIVE = "native";
+
private static PluginManager mInstance = null;
private final Context mContext;
@@ -85,7 +89,7 @@ public class PluginManager {
throw new IllegalStateException(
"First call to PluginManager need a valid context.");
}
- mInstance = new PluginManager(context);
+ mInstance = new PluginManager(context.getApplicationContext());
}
return mInstance;
}
@@ -108,7 +112,8 @@ public class PluginManager {
ArrayList<String> directories = new ArrayList<String>();
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(
- PLUGIN_ACTION), PackageManager.GET_SERVICES);
+ PLUGIN_ACTION), PackageManager.GET_SERVICES
+ | PackageManager.GET_META_DATA);
synchronized(mPackageInfoCache) {
@@ -116,27 +121,35 @@ public class PluginManager {
mPackageInfoCache.clear();
for (ResolveInfo info : plugins) {
+
+ // retrieve the plugin's service information
ServiceInfo serviceInfo = info.serviceInfo;
if (serviceInfo == null) {
Log.w(LOGTAG, "Ignore bad plugin");
continue;
}
+
+ // retrieve information from the plugin's manifest
PackageInfo pkgInfo;
try {
pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
PackageManager.GET_PERMISSIONS
| PackageManager.GET_SIGNATURES);
} catch (NameNotFoundException e) {
- Log.w(LOGTAG, "Cant find plugin: " + serviceInfo.packageName);
+ Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
continue;
}
if (pkgInfo == null) {
continue;
}
+
+ // check if their is a conflict in the lib directory names
String directory = pkgInfo.applicationInfo.dataDir + "/lib";
if (directories.contains(directory)) {
continue;
}
+
+ // check if the plugin has the required permissions
String permissions[] = pkgInfo.requestedPermissions;
if (permissions == null) {
continue;
@@ -151,6 +164,8 @@ public class PluginManager {
if (!permissionOk) {
continue;
}
+
+ // check to ensure the plugin is properly signed
Signature signatures[] = pkgInfo.signatures;
if (signatures == null) {
continue;
@@ -169,6 +184,39 @@ public class PluginManager {
continue;
}
}
+
+ // determine the type of plugin from the manifest
+ if (serviceInfo.metaData == null) {
+ Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined");
+ continue;
+ }
+
+ String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
+ if (!TYPE_NATIVE.equals(pluginType)) {
+ Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
+ continue;
+ }
+
+ try {
+ Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
+
+ //TODO implement any requirements of the plugin class here!
+ boolean classFound = true;
+
+ if (!classFound) {
+ Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
+ continue;
+ }
+
+ } catch (NameNotFoundException e) {
+ Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
+ continue;
+ } catch (ClassNotFoundException e) {
+ Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
+ continue;
+ }
+
+ // if all checks have passed then make the plugin available
mPackageInfoCache.add(pkgInfo);
directories.add(directory);
}
@@ -177,6 +225,7 @@ public class PluginManager {
return directories.toArray(new String[directories.size()]);
}
+ /* package */
String getPluginsAPKName(String pluginLib) {
// basic error checking on input params
@@ -200,4 +249,14 @@ public class PluginManager {
String getPluginSharedDataDirectory() {
return mContext.getDir("plugins", 0).getPath();
}
+
+ /* package */
+ Class<?> getPluginClass(String packageName, String className)
+ throws NameNotFoundException, ClassNotFoundException {
+ Context pluginContext = mContext.createPackageContext(packageName,
+ Context.CONTEXT_INCLUDE_CODE |
+ Context.CONTEXT_IGNORE_SECURITY);
+ ClassLoader pluginCL = pluginContext.getClassLoader();
+ return pluginCL.loadClass(className);
+ }
}
diff --git a/core/java/android/webkit/PluginUtil.java b/core/java/android/webkit/PluginUtil.java
deleted file mode 100644
index 8fdbd67..0000000
--- a/core/java/android/webkit/PluginUtil.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 android.webkit;
-
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Log;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-
-class PluginUtil {
-
- private static final String LOGTAG = "PluginUtil";
-
- /**
- *
- * @param packageName the name of the apk where the class can be found
- * @param className the fully qualified name of a subclass of PluginStub
- */
- /* package */
- static PluginStub getPluginStub(Context context, String packageName,
- String className) {
- try {
- Context pluginContext = context.createPackageContext(packageName,
- Context.CONTEXT_INCLUDE_CODE |
- Context.CONTEXT_IGNORE_SECURITY);
- ClassLoader pluginCL = pluginContext.getClassLoader();
-
- Class<?> stubClass = pluginCL.loadClass(className);
- Object stubObject = stubClass.newInstance();
-
- if (stubObject instanceof PluginStub) {
- return (PluginStub) stubObject;
- } else {
- Log.e(LOGTAG, "The plugin class is not of type PluginStub");
- }
- } catch (Exception e) {
- // Any number of things could have happened. Log the exception and
- // return null. Careful not to use Log.e(LOGTAG, "String", e)
- // because that reports the exception to the checkin service.
- Log.e(LOGTAG, Log.getStackTraceString(e));
- }
- return null;
- }
-}
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 232ed36..7c5f2b0 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -28,8 +28,14 @@ import android.util.Log;
public final class URLUtil {
private static final String LOGTAG = "webkit";
-
+
+ // to refer to bar.png under your package's asset/foo/ directory, use
+ // "file:///android_asset/foo/bar.png".
static final String ASSET_BASE = "file:///android_asset/";
+ // to refer to bar.png under your package's res/drawable/ directory, use
+ // "file:///android_res/drawable/bar.png". Use "drawable" to refer to
+ // "drawable-hdpi" directory as well.
+ static final String RESOURCE_BASE = "file:///android_res/";
static final String FILE_BASE = "file://";
static final String PROXY_BASE = "file:///cookieless_proxy/";
@@ -166,7 +172,15 @@ public final class URLUtil {
public static boolean isAssetUrl(String url) {
return (null != url) && url.startsWith(ASSET_BASE);
}
-
+
+ /**
+ * @return True iff the url is a resource file.
+ * @hide
+ */
+ public static boolean isResourceUrl(String url) {
+ return (null != url) && url.startsWith(RESOURCE_BASE);
+ }
+
/**
* @return True iff the url is an proxy url to allow cookieless network
* requests from a file url.
@@ -251,6 +265,7 @@ public final class URLUtil {
}
return (isAssetUrl(url) ||
+ isResourceUrl(url) ||
isFileUrl(url) ||
isAboutUrl(url) ||
isHttpUrl(url) ||
@@ -367,19 +382,23 @@ public final class URLUtil {
/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
- Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
+ Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
+ Pattern.CASE_INSENSITIVE);
/*
* Parse the Content-Disposition HTTP Header. The format of the header
* is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
* This header provides a filename for content that is going to be
* downloaded to the file system. We only support the attachment type.
+ * Note that RFC 2616 specifies the filename value must be double-quoted.
+ * Unfortunately some servers do not quote the value so to maintain
+ * consistent behaviour with other browsers, we allow unquoted values too.
*/
static String parseContentDisposition(String contentDisposition) {
try {
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
if (m.find()) {
- return m.group(1);
+ return m.group(2);
}
} catch (IllegalStateException ex) {
// This function is defined as returning null when it can't parse the header
diff --git a/core/java/android/webkit/ViewManager.java b/core/java/android/webkit/ViewManager.java
index 6a838c3..75db0a0 100644
--- a/core/java/android/webkit/ViewManager.java
+++ b/core/java/android/webkit/ViewManager.java
@@ -16,7 +16,6 @@
package android.webkit;
-import android.content.Context;
import android.view.View;
import android.widget.AbsoluteLayout;
@@ -26,6 +25,7 @@ class ViewManager {
private final WebView mWebView;
private final ArrayList<ChildView> mChildren = new ArrayList<ChildView>();
private boolean mHidden;
+ private boolean mReadyToDraw;
class ChildView {
int x;
@@ -70,6 +70,9 @@ class ViewManager {
void attachViewOnUIThread(AbsoluteLayout.LayoutParams lp) {
mWebView.addView(mView, lp);
mChildren.add(this);
+ if (!mReadyToDraw) {
+ mView.setVisibility(View.GONE);
+ }
}
void removeView() {
@@ -154,4 +157,23 @@ class ViewManager {
}
mHidden = false;
}
+
+ void postResetStateAll() {
+ mWebView.mPrivateHandler.post(new Runnable() {
+ public void run() {
+ mReadyToDraw = false;
+ }
+ });
+ }
+
+ void postReadyToDrawAll() {
+ mWebView.mPrivateHandler.post(new Runnable() {
+ public void run() {
+ mReadyToDraw = true;
+ for (ChildView v : mChildren) {
+ v.mView.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 8ca4142..f40b55c 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Message;
import android.view.View;
@@ -294,4 +295,13 @@ public class WebChromeClient {
public void getVisitedHistory(ValueCallback<String[]> callback) {
}
+ /**
+ * Tell the client to open a file chooser.
+ * @param uploadFile A ValueCallback to set the URI of the file to upload.
+ * onReceiveValue must be called to wake up the thread.
+ * @hide
+ */
+ public void openFileChooser(ValueCallback<Uri> uploadFile) {
+ uploadFile.onReceiveValue(null);
+ }
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 6f3262a..d5bb572 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -152,6 +152,7 @@ public class WebSettings {
private int mMinimumLogicalFontSize = 8;
private int mDefaultFontSize = 16;
private int mDefaultFixedFontSize = 13;
+ private int mPageCacheCapacity = 0;
private boolean mLoadsImagesAutomatically = true;
private boolean mBlockNetworkImage = false;
private boolean mBlockNetworkLoads;
@@ -879,6 +880,20 @@ public class WebSettings {
}
/**
+ * Set the number of pages cached by the WebKit for the history navigation.
+ * @param size A non-negative integer between 0 (no cache) and 20 (max).
+ * @hide
+ */
+ public synchronized void setPageCacheCapacity(int size) {
+ if (size < 0) size = 0;
+ if (size > 20) size = 20;
+ if (mPageCacheCapacity != size) {
+ mPageCacheCapacity = size;
+ postSync();
+ }
+ }
+
+ /**
* Tell the WebView to load image resources automatically.
* @param flag True if the WebView should load images automatically.
*/
@@ -1002,7 +1017,8 @@ public class WebSettings {
* should never be null.
*/
public synchronized void setGeolocationDatabasePath(String databasePath) {
- if (databasePath != null && !databasePath.equals(mDatabasePath)) {
+ if (databasePath != null
+ && !databasePath.equals(mGeolocationDatabasePath)) {
mGeolocationDatabasePath = databasePath;
postSync();
}
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index a182287..cf71a84 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -389,8 +389,8 @@ public final class WebStorage {
mOrigins = new HashMap<String, Origin>();
for (String origin : tmp) {
Origin website = new Origin(origin,
- nativeGetUsageForOrigin(origin),
- nativeGetQuotaForOrigin(origin));
+ nativeGetQuotaForOrigin(origin),
+ nativeGetUsageForOrigin(origin));
mOrigins.put(origin, website);
}
}
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index e0d41c2..b6891b1 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -84,14 +84,22 @@ import java.util.ArrayList;
// True if the most recent drag event has caused either the TextView to
// scroll or the web page to scroll. Gets reset after a touch down.
private boolean mScrolled;
- // Gets set to true when the the IME jumps to the next textfield. When this
- // happens, the next time the user hits a key it is okay for the focus
- // pointer to not match the WebTextView's node pointer
- boolean mOkayForFocusNotToMatch;
// Whether or not a selection change was generated from webkit. If it was,
// we do not need to pass the selection back to webkit.
private boolean mFromWebKit;
+ // Whether or not a selection change was generated from the WebTextView
+ // gaining focus. If it is, we do not want to pass it to webkit. This
+ // selection comes from the MovementMethod, but we behave differently. If
+ // WebTextView gained focus from a touch, webkit will determine the
+ // selection.
+ private boolean mFromFocusChange;
+ // Whether or not a selection change was generated from setInputType. We
+ // do not want to pass this change to webkit.
+ private boolean mFromSetInputType;
private boolean mGotTouchDown;
+ // Keep track of whether a long press has happened. Only meaningful after
+ // an ACTION_DOWN MotionEvent
+ private boolean mHasPerformedLongClick;
private boolean mInSetTextAndKeepSelection;
// Array to store the final character added in onTextChanged, so that its
// KeyEvents may be determined.
@@ -136,19 +144,6 @@ import java.util.ArrayList;
isArrowKey = true;
break;
}
- if (!isArrowKey && !mOkayForFocusNotToMatch
- && mWebView.nativeFocusNodePointer() != mNodePointer) {
- mWebView.nativeClearCursor();
- // Do not call remove() here, which hides the soft keyboard. If
- // the soft keyboard is being displayed, the user will still want
- // it there.
- mWebView.removeView(this);
- mWebView.requestFocus();
- return mWebView.dispatchKeyEvent(event);
- }
- // After a jump to next textfield and the first key press, the cursor
- // and focus will once again match, so reset this value.
- mOkayForFocusNotToMatch = false;
Spannable text = (Spannable) getText();
int oldLength = text.length();
@@ -185,7 +180,7 @@ import java.util.ArrayList;
}
// Center key should be passed to a potential onClick
if (!down) {
- mWebView.shortPressOnTextField();
+ mWebView.centerKeyPressOnTextField();
}
// Pass to super to handle longpress.
return super.dispatchKeyEvent(event);
@@ -303,20 +298,21 @@ import java.util.ArrayList;
public void onEditorAction(int actionCode) {
switch (actionCode) {
case EditorInfo.IME_ACTION_NEXT:
+ // Since the cursor will no longer be in the same place as the
+ // focus, set the focus controller back to inactive
+ mWebView.setFocusControllerInactive();
mWebView.nativeMoveCursorToNextTextInput();
// Preemptively rebuild the WebTextView, so that the action will
// be set properly.
mWebView.rebuildWebTextView();
- // Since the cursor will no longer be in the same place as the
- // focus, set the focus controller back to inactive
- mWebView.setFocusControllerInactive();
+ setDefaultSelection();
mWebView.invalidate();
- mOkayForFocusNotToMatch = true;
break;
case EditorInfo.IME_ACTION_DONE:
super.onEditorAction(actionCode);
break;
case EditorInfo.IME_ACTION_GO:
+ case EditorInfo.IME_ACTION_SEARCH:
// Send an enter and hide the soft keyboard
InputMethodManager.getInstance(mContext)
.hideSoftInputFromWindow(getWindowToken(), 0);
@@ -331,6 +327,14 @@ import java.util.ArrayList;
}
@Override
+ protected void onFocusChanged(boolean focused, int direction,
+ Rect previouslyFocusedRect) {
+ mFromFocusChange = true;
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ mFromFocusChange = false;
+ }
+
+ @Override
protected void onSelectionChanged(int selStart, int selEnd) {
// This code is copied from TextView.onDraw(). That code does not get
// executed, however, because the WebTextView does not draw, allowing
@@ -342,7 +346,8 @@ import java.util.ArrayList;
int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
}
- if (!mFromWebKit && mWebView != null) {
+ if (!mFromWebKit && !mFromFocusChange && !mFromSetInputType
+ && mWebView != null) {
if (DebugFlags.WEB_TEXT_VIEW) {
Log.v(LOGTAG, "onSelectionChanged selStart=" + selStart
+ " selEnd=" + selEnd);
@@ -426,8 +431,13 @@ import java.util.ArrayList;
mDragSent = false;
mScrolled = false;
mGotTouchDown = true;
+ mHasPerformedLongClick = false;
break;
case MotionEvent.ACTION_MOVE:
+ if (mHasPerformedLongClick) {
+ mGotTouchDown = false;
+ return false;
+ }
int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
Spannable buffer = getText();
int initialScrollX = Touch.getInitialScrollX(this, buffer);
@@ -451,6 +461,7 @@ import java.util.ArrayList;
mScrollX / maxScrollX : 0, mScrollY);
}
mScrolled = true;
+ cancelLongPress();
return true;
}
if (Math.abs((int) event.getX() - mDragStartX) < slop
@@ -477,6 +488,10 @@ import java.util.ArrayList;
return false;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ if (mHasPerformedLongClick) {
+ mGotTouchDown = false;
+ return false;
+ }
if (!mScrolled) {
// If the page scrolled, or the TextView scrolled, we do not
// want to change the selection
@@ -520,6 +535,12 @@ import java.util.ArrayList;
return false;
}
+ @Override
+ public boolean performLongClick() {
+ mHasPerformedLongClick = true;
+ return super.performLongClick();
+ }
+
/**
* Remove this WebTextView from its host WebView, and return
* focus to the host.
@@ -552,7 +573,8 @@ import java.util.ArrayList;
*/
public void setAdapterCustom(AutoCompleteAdapter adapter) {
if (adapter != null) {
- setInputType(EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+ setInputType(getInputType()
+ | EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
adapter.setTextView(this);
}
super.setAdapter(adapter);
@@ -591,6 +613,32 @@ import java.util.ArrayList;
}
/**
+ * Sets the selection when the user clicks on a textfield or textarea with
+ * the trackball or center key, or starts typing into it without clicking on
+ * it.
+ */
+ /* package */ void setDefaultSelection() {
+ Spannable text = (Spannable) getText();
+ int selection = mSingle ? text.length() : 0;
+ if (Selection.getSelectionStart(text) == selection
+ && Selection.getSelectionEnd(text) == selection) {
+ // The selection of the UI copy is set correctly, but the
+ // WebTextView still needs to inform the webkit thread to set the
+ // selection. Normally that is done in onSelectionChanged, but
+ // onSelectionChanged will not be called because the UI copy is not
+ // changing. (This can happen when the WebTextView takes focus.
+ // That onSelectionChanged was blocked because the selection set
+ // when focusing is not necessarily the desirable selection for
+ // WebTextView.)
+ if (mWebView != null) {
+ mWebView.setSelection(selection, selection);
+ }
+ } else {
+ Selection.setSelection(text, selection, selection);
+ }
+ }
+
+ /**
* Determine whether to use the system-wide password disguising method,
* or to use none.
* @param inPassword True if the textfield is a password field.
@@ -660,7 +708,14 @@ import java.util.ArrayList;
setTextColor(Color.BLACK);
}
- /* package */ void setMaxLength(int maxLength) {
+ @Override
+ public void setInputType(int type) {
+ mFromSetInputType = true;
+ super.setInputType(type);
+ mFromSetInputType = false;
+ }
+
+ private void setMaxLength(int maxLength) {
mMaxLength = maxLength;
if (-1 == maxLength) {
setFilters(NO_FILTERS);
@@ -708,7 +763,6 @@ import java.util.ArrayList;
// Set up a measure spec so a layout can always be recreated.
mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
- requestFocus();
}
/**
@@ -725,68 +779,6 @@ import java.util.ArrayList;
}
/**
- * Set whether this is a single-line textfield or a multi-line textarea.
- * Textfields scroll horizontally, and do not handle the enter key.
- * Textareas behave oppositely.
- * Do NOT call this after calling setInPassword(true). This will result in
- * removing the password input type.
- */
- public void setSingleLine(boolean single) {
- int inputType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
- if (single) {
- int action = mWebView.nativeTextFieldAction();
- switch (action) {
- // Keep in sync with CachedRoot::ImeAction
- case 0: // NEXT
- setImeOptions(EditorInfo.IME_ACTION_NEXT);
- break;
- case 1: // GO
- setImeOptions(EditorInfo.IME_ACTION_GO);
- break;
- case -1: // FAILURE
- case 2: // DONE
- setImeOptions(EditorInfo.IME_ACTION_DONE);
- break;
- }
- } else {
- inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
- | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
- | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
- setImeOptions(EditorInfo.IME_ACTION_NONE);
- }
- mSingle = single;
- setHorizontallyScrolling(single);
- setInputType(inputType);
- }
-
- /**
- * Set the text for this WebTextView, and set the selection to (start, end)
- * @param text Text to go into this WebTextView.
- * @param start Beginning of the selection.
- * @param end End of the selection.
- */
- /* package */ void setText(CharSequence text, int start, int end) {
- mPreChange = text.toString();
- setText(text);
- Spannable span = (Spannable) getText();
- int length = span.length();
- if (end > length) {
- end = length;
- }
- if (start < 0) {
- start = 0;
- } else if (start > length) {
- start = length;
- }
- if (DebugFlags.WEB_TEXT_VIEW) {
- Log.v(LOGTAG, "setText start=" + start
- + " end=" + end);
- }
- Selection.setSelection(span, start, end);
- }
-
- /**
* Set the text to the new string, but use the old selection, making sure
* to keep it within the new string.
* @param text The new text to place in the textfield.
@@ -801,6 +793,93 @@ import java.util.ArrayList;
}
/**
+ * Called by WebView.rebuildWebTextView(). Based on the type of the <input>
+ * element, set up the WebTextView, its InputType, and IME Options properly.
+ * @param type int corresponding to enum "type" defined in WebView.cpp.
+ * Does not correspond to HTMLInputElement::InputType so this
+ * is unaffected if that changes, and also because that has no
+ * type corresponding to textarea (which is its own tag).
+ */
+ /* package */ void setType(int type) {
+ if (mWebView == null) return;
+ boolean single = true;
+ boolean inPassword = false;
+ int maxLength = -1;
+ int inputType = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
+ switch (type) {
+ case 1: // TEXT_AREA
+ single = false;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
+ | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
+ setImeOptions(EditorInfo.IME_ACTION_NONE);
+ break;
+ case 2: // PASSWORD
+ inPassword = true;
+ break;
+ case 3: // SEARCH
+ setImeOptions(EditorInfo.IME_ACTION_SEARCH);
+ break;
+ case 4: // EMAIL
+ // TYPE_TEXT_VARIATION_WEB_EDIT_TEXT prevents EMAIL_ADDRESS
+ // from working, so exclude it for now.
+ inputType = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+ break;
+ case 5: // NUMBER
+ inputType = EditorInfo.TYPE_CLASS_NUMBER;
+ break;
+ case 6: // TELEPHONE
+ inputType = EditorInfo.TYPE_CLASS_PHONE;
+ break;
+ case 7: // URL
+ // TYPE_TEXT_VARIATION_WEB_EDIT_TEXT prevents URI
+ // from working, so exclude it for now.
+ inputType = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_URI;
+ break;
+ default:
+ break;
+ }
+ setHint(null);
+ if (single) {
+ mWebView.requestLabel(mWebView.nativeFocusCandidateFramePointer(),
+ mNodePointer);
+ maxLength = mWebView.nativeFocusCandidateMaxLength();
+ if (type != 2 /* PASSWORD */) {
+ String name = mWebView.nativeFocusCandidateName();
+ if (name != null && name.length() > 0) {
+ mWebView.requestFormData(name, mNodePointer);
+ }
+ }
+ if (type != 3 /* SEARCH */) {
+ int action = mWebView.nativeTextFieldAction();
+ switch (action) {
+ // Keep in sync with CachedRoot::ImeAction
+ case 0: // NEXT
+ setImeOptions(EditorInfo.IME_ACTION_NEXT);
+ break;
+ case 1: // GO
+ setImeOptions(EditorInfo.IME_ACTION_GO);
+ break;
+ case -1: // FAILURE
+ case 2: // DONE
+ setImeOptions(EditorInfo.IME_ACTION_DONE);
+ break;
+ }
+ }
+ }
+ mSingle = single;
+ setMaxLength(maxLength);
+ setHorizontallyScrolling(single);
+ setInputType(inputType);
+ setInPassword(inPassword);
+ AutoCompleteAdapter adapter = null;
+ setAdapterCustom(adapter);
+ }
+
+ /**
* Update the cache to reflect the current text.
*/
/* package */ void updateCachedTextfield() {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index f6d6d22..c349606 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
-import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -29,7 +28,6 @@ import android.graphics.Color;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
import android.net.Uri;
@@ -55,7 +53,6 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AlphaAnimation;
import android.view.inputmethod.InputMethodManager;
@@ -65,7 +62,9 @@ import android.widget.AbsoluteLayout;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Scroller;
import android.widget.Toast;
@@ -83,6 +82,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import junit.framework.Assert;
+
/**
* <p>A View that displays web pages. This class is the basis upon which you
* can roll your own web browser or simply display some online content within your Activity.
@@ -199,11 +200,16 @@ public class WebView extends AbsoluteLayout
implements ViewTreeObserver.OnGlobalFocusChangeListener,
ViewGroup.OnHierarchyChangeListener {
+ // enable debug output for drag trackers
+ private static final boolean DEBUG_DRAG_TRACKER = false;
// if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
// the screen all-the-time. Good for profiling our drawing code
static private final boolean AUTO_REDRAW_HACK = false;
// true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
private boolean mAutoRedraw;
+ private int mRootLayer; // C++ pointer to the root layer
+ private boolean mLayersHaveAnimations;
+ private EvaluateLayersAnimations mEvaluateThread;
static final String LOGTAG = "webview";
@@ -298,6 +304,9 @@ public class WebView extends AbsoluteLayout
// Used by WebViewCore to create child views.
/* package */ final ViewManager mViewManager;
+ // Used to display in full screen mode
+ PluginFullScreenHolder mFullScreenHolder;
+
/**
* Position of the last touch event.
*/
@@ -350,7 +359,6 @@ public class WebView extends AbsoluteLayout
private static final int TOUCH_DOUBLE_TAP_MODE = 6;
private static final int TOUCH_DONE_MODE = 7;
private static final int TOUCH_SELECT_MODE = 8;
- private static final int TOUCH_PINCH_DRAG = 9;
// Whether to forward the touch events to WebCore
private boolean mForwardTouchEvents = false;
@@ -363,6 +371,19 @@ public class WebView extends AbsoluteLayout
private static final int PREVENT_DRAG_YES = 2;
private int mPreventDrag = PREVENT_DRAG_NO;
+ // by default mPreventLongPress is false. If it is true, long press event
+ // will be handled by WebKit instead of UI.
+ private boolean mPreventLongPress = false;
+ // by default mPreventDoubleTap is false. If it is true, double tap event
+ // will be handled by WebKit instead of UI.
+ private boolean mPreventDoubleTap = false;
+
+ // this needs to be in sync with the logic in WebKit's
+ // EventHandler::handleTouchEvent()
+ private static final int TOUCH_PREVENT_DRAG = 0x1;
+ private static final int TOUCH_PREVENT_LONGPRESS = 0x2;
+ private static final int TOUCH_PREVENT_DOUBLETAP = 0x4;
+
// To keep track of whether the current drag was initiated by a WebTextView,
// so that we know not to hide the cursor
boolean mDragFromTextInput;
@@ -392,6 +413,8 @@ public class WebView extends AbsoluteLayout
private static final int LONG_PRESS_TIMEOUT = 1000;
// needed to avoid flinging after a pause of no movement
private static final int MIN_FLING_TIME = 250;
+ // draw unfiltered after drag is held without movement
+ private static final int MOTIONLESS_TIME = 100;
// The time that the Zoom Controls are visible before fading away
private static final long ZOOM_CONTROLS_TIMEOUT =
ViewConfiguration.getZoomControlsTimeout();
@@ -429,6 +452,10 @@ public class WebView extends AbsoluteLayout
private Scroller mScroller;
private boolean mWrapContent;
+ private static final int MOTIONLESS_FALSE = 0;
+ private static final int MOTIONLESS_PENDING = 1;
+ private static final int MOTIONLESS_TRUE = 2;
+ private int mHeldMotionless;
/**
* Private message ids
@@ -440,6 +467,8 @@ public class WebView extends AbsoluteLayout
private static final int RELEASE_SINGLE_TAP = 5;
private static final int REQUEST_FORM_DATA = 6;
private static final int RESUME_WEBCORE_UPDATE = 7;
+ private static final int DRAG_HELD_MOTIONLESS = 8;
+ private static final int AWAKEN_SCROLL_BARS = 9;
//! arg1=x, arg2=y
static final int SCROLL_TO_MSG_ID = 10;
@@ -452,17 +481,24 @@ public class WebView extends AbsoluteLayout
static final int UPDATE_TEXT_ENTRY_MSG_ID = 15;
static final int WEBCORE_INITIALIZED_MSG_ID = 16;
static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17;
+ static final int FIND_AGAIN = 18;
static final int MOVE_OUT_OF_PLUGIN = 19;
static final int CLEAR_TEXT_ENTRY = 20;
static final int UPDATE_TEXT_SELECTION_MSG_ID = 21;
- static final int UPDATE_CLIPBOARD = 22;
+
static final int LONG_PRESS_CENTER = 23;
static final int PREVENT_TOUCH_ID = 24;
static final int WEBCORE_NEED_TOUCH_EVENTS = 25;
// obj=Rect in doc coordinates
static final int INVAL_RECT_MSG_ID = 26;
static final int REQUEST_KEYBOARD = 27;
- static final int SHOW_RECT_MSG_ID = 28;
+ static final int DO_MOTION_UP = 28;
+ static final int SHOW_FULLSCREEN = 29;
+ static final int HIDE_FULLSCREEN = 30;
+ static final int DOM_FOCUS_CHANGED = 31;
+ static final int IMMEDIATE_REPAINT_MSG_ID = 32;
+ static final int SET_ROOT_LAYER_MSG_ID = 33;
+ static final int RETURN_LABEL = 34;
static final String[] HandlerDebugString = {
"REMEMBER_PASSWORD", // = 1;
@@ -471,9 +507,9 @@ public class WebView extends AbsoluteLayout
"SWITCH_TO_LONGPRESS", // = 4;
"RELEASE_SINGLE_TAP", // = 5;
"REQUEST_FORM_DATA", // = 6;
- "SWITCH_TO_CLICK", // = 7;
- "RESUME_WEBCORE_UPDATE", // = 8;
- "9",
+ "RESUME_WEBCORE_UPDATE", // = 7;
+ "DRAG_HELD_MOTIONLESS", // = 8;
+ "AWAKEN_SCROLL_BARS", // = 9;
"SCROLL_TO_MSG_ID", // = 10;
"SCROLL_BY_MSG_ID", // = 11;
"SPAWN_SCROLL_TO_MSG_ID", // = 12;
@@ -482,19 +518,35 @@ public class WebView extends AbsoluteLayout
"UPDATE_TEXT_ENTRY_MSG_ID", // = 15;
"WEBCORE_INITIALIZED_MSG_ID", // = 16;
"UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17;
- "18", // = 18;
+ "FIND_AGAIN", // = 18;
"MOVE_OUT_OF_PLUGIN", // = 19;
"CLEAR_TEXT_ENTRY", // = 20;
"UPDATE_TEXT_SELECTION_MSG_ID", // = 21;
- "UPDATE_CLIPBOARD", // = 22;
+ "22", // = 22;
"LONG_PRESS_CENTER", // = 23;
"PREVENT_TOUCH_ID", // = 24;
"WEBCORE_NEED_TOUCH_EVENTS", // = 25;
"INVAL_RECT_MSG_ID", // = 26;
"REQUEST_KEYBOARD", // = 27;
- "SHOW_RECT_MSG_ID" // = 28;
+ "DO_MOTION_UP", // = 28;
+ "SHOW_FULLSCREEN", // = 29;
+ "HIDE_FULLSCREEN", // = 30;
+ "DOM_FOCUS_CHANGED", // = 31;
+ "IMMEDIATE_REPAINT_MSG_ID", // = 32;
+ "SET_ROOT_LAYER_MSG_ID", // = 33;
+ "RETURN_LABEL" // = 34;
};
+ // If the site doesn't use the viewport meta tag to specify the viewport,
+ // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
+ static final int DEFAULT_VIEWPORT_WIDTH = 800;
+
+ // normally we try to fit the content to the minimum preferred width
+ // calculated by the Webkit. To avoid the bad behavior when some site's
+ // minimum preferred width keeps growing when changing the viewport width or
+ // the minimum preferred width is huge, an upper limit is needed.
+ static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
+
// default scale limit. Depending on the display density
private static float DEFAULT_MAX_ZOOM_SCALE;
private static float DEFAULT_MIN_ZOOM_SCALE;
@@ -514,8 +566,8 @@ public class WebView extends AbsoluteLayout
// ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
// engadget always have wider mContentWidth no matter what viewport size is.
- int mZoomOverviewWidth = WebViewCore.DEFAULT_VIEWPORT_WIDTH;
- float mTextWrapScale;
+ int mZoomOverviewWidth = DEFAULT_VIEWPORT_WIDTH;
+ float mLastScale;
// default scale. Depending on the display density.
static int DEFAULT_SCALE_PERCENT;
@@ -539,11 +591,10 @@ public class WebView extends AbsoluteLayout
private boolean mUserScroll = false;
private int mSnapScrollMode = SNAP_NONE;
- private static final int SNAP_NONE = 1;
- private static final int SNAP_X = 2;
- private static final int SNAP_Y = 3;
- private static final int SNAP_X_LOCK = 4;
- private static final int SNAP_Y_LOCK = 5;
+ private static final int SNAP_NONE = 0;
+ private static final int SNAP_LOCK = 1; // not a separate state
+ private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
+ private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
private boolean mSnapPositive;
// Used to match key downs and key ups
@@ -669,6 +720,9 @@ public class WebView extends AbsoluteLayout
public void onVisibilityChanged(boolean visible) {
if (visible) {
switchOutDrawHistory();
+ // Bring back the hidden zoom controls.
+ mZoomButtonsController.getZoomControls().setVisibility(
+ View.VISIBLE);
updateZoomButtonsEnabled();
}
}
@@ -714,7 +768,7 @@ public class WebView extends AbsoluteLayout
/**
* Construct a new WebView with layout parameters, a default style and a set
* of custom Javscript interfaces to be added to the WebView at initialization
- * time. This guraratees that these interfaces will be available when the JS
+ * time. This guarantees that these interfaces will be available when the JS
* context is initialized.
* @param context A Context object used to access application assets.
* @param attrs An AttributeSet passed to our parent.
@@ -729,12 +783,11 @@ public class WebView extends AbsoluteLayout
init();
mCallbackProxy = new CallbackProxy(context, this);
+ mViewManager = new ViewManager(this);
mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
mDatabase = WebViewDatabase.getInstance(context);
mScroller = new Scroller(context);
- mViewManager = new ViewManager(this);
-
mZoomButtonsController = new ZoomButtonsController(this);
mZoomButtonsController.setOnZoomListener(mZoomListener);
// ZoomButtonsController positions the buttons at the bottom, but in
@@ -747,9 +800,6 @@ public class WebView extends AbsoluteLayout
params;
frameParams.gravity = Gravity.RIGHT;
}
-
- mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
}
private void updateZoomButtonsEnabled() {
@@ -760,9 +810,6 @@ public class WebView extends AbsoluteLayout
// button, if the page cannot zoom
mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
} else {
- // Bring back the hidden zoom controls.
- mZoomButtonsController.getZoomControls()
- .setVisibility(View.VISIBLE);
// Set each one individually, as a page may be able to zoom in
// or out.
mZoomButtonsController.setZoomInEnabled(canZoomIn);
@@ -792,7 +839,6 @@ public class WebView extends AbsoluteLayout
mDefaultScale = density;
mActualScale = density;
mInvActualScale = 1 / density;
- mTextWrapScale = density;
DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
@@ -813,7 +859,7 @@ public class WebView extends AbsoluteLayout
mDefaultScale = density;
mMaxZoomScale *= scaleFactor;
mMinZoomScale *= scaleFactor;
- setNewZoomScale(mActualScale * scaleFactor, true, false);
+ setNewZoomScale(mActualScale * scaleFactor, false);
}
}
@@ -1170,8 +1216,9 @@ public class WebView extends AbsoluteLayout
b.putInt("scrollX", mScrollX);
b.putInt("scrollY", mScrollY);
b.putFloat("scale", mActualScale);
- b.putFloat("textwrapScale", mTextWrapScale);
- b.putBoolean("overview", mInZoomOverview);
+ if (mInZoomOverview) {
+ b.putFloat("lastScale", mLastScale);
+ }
return true;
}
return false;
@@ -1216,8 +1263,13 @@ public class WebView extends AbsoluteLayout
// onSizeChanged() is called, the rest will be set
// correctly
mActualScale = scale;
- mTextWrapScale = b.getFloat("textwrapScale", scale);
- mInZoomOverview = b.getBoolean("overview");
+ float lastScale = b.getFloat("lastScale", -1.0f);
+ if (lastScale > 0) {
+ mInZoomOverview = true;
+ mLastScale = lastScale;
+ } else {
+ mInZoomOverview = false;
+ }
invalidate();
return true;
}
@@ -1684,6 +1736,13 @@ public class WebView extends AbsoluteLayout
return result;
}
+ // Called by JNI when the DOM has changed the focus. Clear the focus so
+ // that new keys will go to the newly focused field
+ private void domChangedFocus() {
+ if (inEditingMode()) {
+ mPrivateHandler.obtainMessage(DOM_FOCUS_CHANGED).sendToTarget();
+ }
+ }
/**
* Request the href of an anchor element due to getFocusNodePath returning
* "href." If hrefMsg is null, this method returns immediately and does not
@@ -1787,7 +1846,7 @@ public class WebView extends AbsoluteLayout
}
if (null != v) {
addView(v, new AbsoluteLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0));
if (mTitleShadow == null) {
mTitleShadow = (Drawable) mContext.getResources().getDrawable(
@@ -1940,18 +1999,12 @@ public class WebView extends AbsoluteLayout
contentSizeChanged(updateLayout);
}
- private void setNewZoomScale(float scale, boolean updateTextWrapScale,
- boolean force) {
+ private void setNewZoomScale(float scale, boolean force) {
if (scale < mMinZoomScale) {
scale = mMinZoomScale;
} else if (scale > mMaxZoomScale) {
scale = mMaxZoomScale;
}
- if (updateTextWrapScale) {
- mTextWrapScale = scale;
- // reset mLastHeightSent to force VIEW_SIZE_CHANGED sent to WebKit
- mLastHeightSent = 0;
- }
if (scale != mActualScale || force) {
if (mDrawHistory) {
// If history Picture is drawn, don't update scroll. They will
@@ -1961,7 +2014,9 @@ public class WebView extends AbsoluteLayout
}
mActualScale = scale;
mInvActualScale = 1 / scale;
- sendViewSizeZoom();
+ if (!mPreviewZoomOnly) {
+ sendViewSizeZoom();
+ }
} else {
// update our scroll so we don't appear to jump
// i.e. keep the center of the doc in the center of the view
@@ -1989,9 +2044,10 @@ public class WebView extends AbsoluteLayout
mScrollX = pinLocX(Math.round(sx));
mScrollY = pinLocY(Math.round(sy));
- // update webkit
- sendViewSizeZoom();
- sendOurVisibleRect();
+ if (!mPreviewZoomOnly) {
+ sendViewSizeZoom();
+ sendOurVisibleRect();
+ }
}
}
}
@@ -2001,8 +2057,6 @@ public class WebView extends AbsoluteLayout
private Rect mLastGlobalRect;
private Rect sendOurVisibleRect() {
- if (mPreviewZoomOnly) return mLastVisibleRectSent;
-
Rect rect = new Rect();
calcOurContentVisibleRect(rect);
// Rect.equals() checks for null input.
@@ -2056,8 +2110,6 @@ public class WebView extends AbsoluteLayout
int mWidth;
int mHeight;
int mTextWrapWidth;
- int mAnchorX;
- int mAnchorY;
float mScale;
boolean mIgnoreHeight;
}
@@ -2069,8 +2121,6 @@ public class WebView extends AbsoluteLayout
* @return true if new values were sent
*/
private boolean sendViewSizeZoom() {
- if (mPreviewZoomOnly) return false;
-
int viewWidth = getViewWidth();
int newWidth = Math.round(viewWidth * mInvActualScale);
int newHeight = Math.round(getViewHeight() * mInvActualScale);
@@ -2090,15 +2140,16 @@ public class WebView extends AbsoluteLayout
ViewSizeData data = new ViewSizeData();
data.mWidth = newWidth;
data.mHeight = newHeight;
- data.mTextWrapWidth = Math.round(viewWidth / mTextWrapScale);;
+ // while in zoom overview mode, the text are wrapped to the screen
+ // width matching mLastScale. So that we don't trigger re-flow while
+ // toggling between overview mode and normal mode.
+ data.mTextWrapWidth = mInZoomOverview ? Math.round(viewWidth
+ / mLastScale) : newWidth;
data.mScale = mActualScale;
data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure;
- data.mAnchorX = mAnchorX;
- data.mAnchorY = mAnchorY;
mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
mLastWidthSent = newWidth;
mLastHeightSent = newHeight;
- mAnchorX = mAnchorY = 0;
return true;
}
return false;
@@ -2360,6 +2411,7 @@ public class WebView extends AbsoluteLayout
}
int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
invalidate();
+ mLastFind = find;
return result;
}
@@ -2367,6 +2419,9 @@ public class WebView extends AbsoluteLayout
// or not we draw the highlights for matches.
private boolean mFindIsUp;
private int mFindHeight;
+ // Keep track of the last string sent, so we can search again after an
+ // orientation change or the dismissal of the soft keyboard.
+ private String mLastFind;
/**
* Return the first substring consisting of the address of a physical
@@ -2422,12 +2477,14 @@ public class WebView extends AbsoluteLayout
* Clear the highlighting surrounding text matches created by findAll.
*/
public void clearMatches() {
+ if (mNativeClass == 0)
+ return;
if (mFindIsUp) {
recordNewContentSize(mContentWidth, mContentHeight - mFindHeight,
false);
mFindIsUp = false;
}
- nativeSetFindIsDown();
+ nativeSetFindIsUp();
// Now that the dialog has been removed, ensure that we scroll to a
// location that is not beyond the end of the page.
pinScrollTo(mScrollX, mScrollY, false, 0);
@@ -2541,6 +2598,41 @@ public class WebView extends AbsoluteLayout
}
}
+ /**
+ * Called by CallbackProxy when the page finishes loading.
+ * @param url The URL of the page which has finished loading.
+ */
+ /* package */ void onPageFinished(String url) {
+ if (mPageThatNeedsToSlideTitleBarOffScreen != null) {
+ // If the user is now on a different page, or has scrolled the page
+ // past the point where the title bar is offscreen, ignore the
+ // scroll request.
+ if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url)
+ && mScrollX == 0 && mScrollY == 0) {
+ pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true,
+ SLIDE_TITLE_DURATION);
+ }
+ mPageThatNeedsToSlideTitleBarOffScreen = null;
+ }
+ }
+
+ /**
+ * The URL of a page that sent a message to scroll the title bar off screen.
+ *
+ * Many mobile sites tell the page to scroll to (0,1) in order to scroll the
+ * title bar off the screen. Sometimes, the scroll position is set before
+ * the page finishes loading. Rather than scrolling while the page is still
+ * loading, keep track of the URL and new scroll position so we can perform
+ * the scroll once the page finishes loading.
+ */
+ private String mPageThatNeedsToSlideTitleBarOffScreen;
+
+ /**
+ * The destination Y scroll position to be used when the page finishes
+ * loading. See mPageThatNeedsToSlideTitleBarOffScreen.
+ */
+ private int mYDistanceToSlideTitleOffScreen;
+
// scale from content to view coordinates, and pin
// return true if pin caused the final x/y different than the request cx/cy,
// and a future scroll may reach the request cx/cy after our size has
@@ -2575,8 +2667,18 @@ public class WebView extends AbsoluteLayout
// page, assume this is an attempt to scroll off the title bar, and
// animate the title bar off screen slowly enough that the user can see
// it.
- if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0) {
- pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
+ if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0
+ && mTitleBar != null) {
+ // FIXME: 100 should be defined somewhere as our max progress.
+ if (getProgress() < 100) {
+ // Wait to scroll the title bar off screen until the page has
+ // finished loading. Keep track of the URL and the destination
+ // Y position
+ mPageThatNeedsToSlideTitleBarOffScreen = getUrl();
+ mYDistanceToSlideTitleOffScreen = vy;
+ } else {
+ pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
+ }
// Since we are animating, we have not yet reached the desired
// scroll position. Do not return true to request another attempt
return false;
@@ -2617,12 +2719,12 @@ public class WebView extends AbsoluteLayout
if (mHeightCanMeasure) {
if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
- && updateLayout) {
+ || updateLayout) {
requestLayout();
}
} else if (mWidthCanMeasure) {
if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
- && updateLayout) {
+ || updateLayout) {
requestLayout();
}
} else {
@@ -2642,6 +2744,16 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Gets the WebViewClient
+ * @return the current WebViewClient instance.
+ *
+ *@hide pending API council approval.
+ */
+ public WebViewClient getWebViewClient() {
+ return mCallbackProxy.getWebViewClient();
+ }
+
+ /**
* Register the interface to be used when content can not be handled by
* the rendering engine, and should be downloaded instead. This will replace
* the current handler.
@@ -2772,16 +2884,7 @@ public class WebView extends AbsoluteLayout
return super.drawChild(canvas, child, drawingTime);
}
- @Override
- protected void onDraw(Canvas canvas) {
- // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
- if (mNativeClass == 0) {
- return;
- }
- int saveCount = canvas.save();
- if (mTitleBar != null) {
- canvas.translate(0, (int) mTitleBar.getHeight());
- }
+ private void drawContent(Canvas canvas) {
// Update the buttons in the picture, so when we draw the picture
// to the screen, they are in the correct state.
// Tell the native side if user is a) touching the screen,
@@ -2791,9 +2894,25 @@ public class WebView extends AbsoluteLayout
// If mNativeClass is 0, we should not reach here, so we do not
// need to check it again.
nativeRecordButtons(hasFocus() && hasWindowFocus(),
- mTouchMode == TOUCH_SHORTPRESS_START_MODE
- || mTrackballDown || mGotCenterDown, false);
+ mTouchMode == TOUCH_SHORTPRESS_START_MODE
+ || mTrackballDown || mGotCenterDown, false);
drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
+ if (mNativeClass == 0) {
+ return;
+ }
+
+ int saveCount = canvas.save();
+ if (mTitleBar != null) {
+ canvas.translate(0, (int) mTitleBar.getHeight());
+ }
+ if (mDragTrackerHandler == null || !mDragTrackerHandler.draw(canvas)) {
+ drawContent(canvas);
+ }
canvas.restoreToCount(saveCount);
// Now draw the shadow.
@@ -2808,6 +2927,7 @@ public class WebView extends AbsoluteLayout
if (AUTO_REDRAW_HACK && mAutoRedraw) {
invalidate();
}
+ mWebViewCore.signalRepaintDone();
}
@Override
@@ -2822,10 +2942,7 @@ public class WebView extends AbsoluteLayout
public boolean performLongClick() {
if (mNativeClass != 0 && nativeCursorIsTextInput()) {
// Send the click so that the textfield is in focus
- // FIXME: When we start respecting changes to the native textfield's
- // selection, need to make sure that this does not change it.
- mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
- nativeCursorNodePointer());
+ centerKeyPressOnTextField();
rebuildWebTextView();
}
if (inEditingMode()) {
@@ -2848,35 +2965,41 @@ public class WebView extends AbsoluteLayout
*/
private boolean mNeedToAdjustWebTextView;
- // if checkVisibility is false, the WebTextView may trigger a move of
- // WebView to bring itself into the view.
- private void adjustTextView(boolean checkVisibility) {
+ private boolean didUpdateTextViewBounds(boolean allowIntersect) {
Rect contentBounds = nativeFocusCandidateNodeBounds();
Rect vBox = contentToViewRect(contentBounds);
Rect visibleRect = new Rect();
calcOurVisibleRect(visibleRect);
- if (!checkVisibility || visibleRect.contains(vBox)) {
- // As a result of the zoom, the textfield is now on
- // screen. Place the WebTextView in its new place,
- // accounting for our new scroll/zoom values.
- mWebTextView
- .setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- contentToViewDimension(nativeFocusCandidateTextSize()));
+ // The IME may have shown, resulting in the textfield being offscreen.
+ // If so, the textfield will be scrolled on screen, so treat it as
+ // though it is on screen. If it is on screen, place the WebTextView in
+ // its new place, accounting for our new scroll/zoom values.
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if ((imm != null && imm.isActive(mWebTextView))
+ || (allowIntersect ? Rect.intersects(visibleRect, vBox)
+ : visibleRect.contains(vBox))) {
mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
vBox.height());
- // If it is a password field, start drawing the
- // WebTextView once again.
- if (nativeFocusCandidateIsPassword()) {
- mWebTextView.setInPassword(true);
- }
+ mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ contentToViewDimension(
+ nativeFocusCandidateTextSize()));
+ return true;
} else {
- // The textfield is now off screen. The user probably
- // was not zooming to see the textfield better. Remove
- // the WebTextView. If the user types a key, and the
+ // The textfield is now off screen. The user probably
+ // was not zooming to see the textfield better. Remove
+ // the WebTextView. If the user types a key, and the
// textfield is still in focus, we will reconstruct
// the WebTextView and scroll it back on screen.
mWebTextView.remove();
+ return false;
+ }
+ }
+
+ private void drawLayers(Canvas canvas) {
+ if (mRootLayer != 0) {
+ float scrollY = Math.max(mScrollY - getTitleHeight(), 0);
+ nativeDrawLayers(mRootLayer, mScrollX, scrollY,
+ mActualScale, canvas);
}
}
@@ -2885,12 +3008,27 @@ public class WebView extends AbsoluteLayout
if (mDrawHistory) {
canvas.scale(mActualScale, mActualScale);
canvas.drawPicture(mHistoryPicture);
+ drawLayers(canvas);
return;
}
boolean animateZoom = mZoomScale != 0;
- boolean animateScroll = !mScroller.isFinished()
- || mVelocityTracker != null;
+ boolean animateScroll = (!mScroller.isFinished()
+ || mVelocityTracker != null)
+ && (mTouchMode != TOUCH_DRAG_MODE ||
+ mHeldMotionless != MOTIONLESS_TRUE);
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ if (mHeldMotionless == MOTIONLESS_PENDING) {
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_FALSE;
+ }
+ if (mHeldMotionless == MOTIONLESS_FALSE) {
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
+ mHeldMotionless = MOTIONLESS_PENDING;
+ }
+ }
if (animateZoom) {
float zoomScale;
int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
@@ -2907,7 +3045,12 @@ public class WebView extends AbsoluteLayout
invalidate();
if (mNeedToAdjustWebTextView) {
mNeedToAdjustWebTextView = false;
- adjustTextView(true);
+ if (didUpdateTextViewBounds(false)
+ && nativeFocusCandidateIsPassword()) {
+ // If it is a password field, start drawing the
+ // WebTextView once again.
+ mWebTextView.setInPassword(true);
+ }
}
}
// calculate the intermediate scroll position. As we need to use
@@ -2941,23 +3084,26 @@ public class WebView extends AbsoluteLayout
canvas.scale(mActualScale, mActualScale);
}
- mWebViewCore.drawContentPicture(canvas, color,
- (animateZoom || mPreviewZoomOnly), animateScroll);
+ mWebViewCore.drawContentPicture(canvas, color, animateZoom,
+ animateScroll);
+
+ drawLayers(canvas);
if (mNativeClass == 0) return;
- if (mShiftIsPressed && !(animateZoom || mPreviewZoomOnly)) {
- if (mTouchSelection) {
+ if (mShiftIsPressed && !animateZoom) {
+ if (mTouchSelection || mExtendSelection) {
nativeDrawSelectionRegion(canvas);
- } else {
- nativeDrawSelection(canvas, mInvActualScale, getTitleHeight(),
- mSelectX, mSelectY, mExtendSelection);
+ }
+ if (!mTouchSelection) {
+ nativeDrawSelectionPointer(canvas, mInvActualScale, mSelectX,
+ mSelectY - getTitleHeight(), mExtendSelection);
}
} else if (drawCursorRing) {
if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
mTouchMode = TOUCH_SHORTPRESS_MODE;
HitTestResult hitTest = getHitTestResult();
- if (hitTest != null &&
- hitTest.mType != HitTestResult.UNKNOWN_TYPE) {
+ if (mPreventLongPress || (hitTest != null &&
+ hitTest.mType != HitTestResult.UNKNOWN_TYPE)) {
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(SWITCH_TO_LONGPRESS),
LONG_PRESS_TIMEOUT);
@@ -2970,6 +3116,15 @@ public class WebView extends AbsoluteLayout
if (mFindIsUp && !animateScroll) {
nativeDrawMatches(canvas);
}
+ if (mFocusSizeChanged) {
+ mFocusSizeChanged = false;
+ // If we are zooming, this will get handled above, when the zoom
+ // finishes. We also do not need to do this unless the WebTextView
+ // is showing.
+ if (!animateZoom && inEditingMode()) {
+ didUpdateTextViewBounds(true);
+ }
+ }
}
// draw history
@@ -3037,24 +3192,22 @@ public class WebView extends AbsoluteLayout
mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
}
- // Called by JNI when a touch event puts a textfield into focus.
+ /**
+ * Called in response to a message from webkit telling us that the soft
+ * keyboard should be launched.
+ */
private void displaySoftKeyboard(boolean isTextView) {
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (isTextView) {
- if (mWebTextView == null) return;
-
+ rebuildWebTextView();
+ if (!inEditingMode()) return;
imm.showSoftInput(mWebTextView, 0);
- if (mActualScale < mDefaultScale) {
- // bring it back to the default scale so that user can enter
- // text.
- mInZoomOverview = false;
- mZoomCenterX = mLastTouchX;
- mZoomCenterY = mLastTouchY;
- // do not change text wrap scale so that there is no reflow
- setNewZoomScale(mDefaultScale, false, false);
- adjustTextView(false);
+ if (mInZoomOverview) {
+ // if in zoom overview mode, call doDoubleTap() to bring it back
+ // to normal mode so that user can enter text.
+ doDoubleTap();
}
}
else { // used by plugins
@@ -3103,6 +3256,8 @@ public class WebView extends AbsoluteLayout
// Note that sendOurVisibleRect calls viewToContent, so the coordinates
// should be in content coordinates.
Rect bounds = nativeFocusCandidateNodeBounds();
+ Rect vBox = contentToViewRect(bounds);
+ mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height());
if (!Rect.intersects(bounds, visibleRect)) {
mWebTextView.bringIntoView();
}
@@ -3113,79 +3268,92 @@ public class WebView extends AbsoluteLayout
// i.e. In the case of opening/closing the screen.
// In that case, we need to set the dimensions, but not the other
// aspects.
- // We also need to restore the selection, which gets wrecked by
- // calling setTextEntryRect.
- Spannable spannable = (Spannable) mWebTextView.getText();
- int start = Selection.getSelectionStart(spannable);
- int end = Selection.getSelectionEnd(spannable);
// If the text has been changed by webkit, update it. However, if
// there has been more UI text input, ignore it. We will receive
// another update when that text is recognized.
- if (text != null && !text.equals(spannable.toString())
+ if (text != null && !text.equals(mWebTextView.getText().toString())
&& nativeTextGeneration() == mTextGeneration) {
mWebTextView.setTextAndKeepSelection(text);
- } else {
- Selection.setSelection(spannable, start, end);
}
} else {
- Rect vBox = contentToViewRect(bounds);
- mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
- vBox.height());
mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ?
Gravity.RIGHT : Gravity.NO_GRAVITY);
- // this needs to be called before update adapter thread starts to
- // ensure the mWebTextView has the same node pointer
+ // This needs to be called before setType, which may call
+ // requestFormData, and it needs to have the correct nodePointer.
mWebTextView.setNodePointer(nodePointer);
- int maxLength = -1;
- boolean isTextField = nativeFocusCandidateIsTextField();
- if (isTextField) {
- maxLength = nativeFocusCandidateMaxLength();
- String name = nativeFocusCandidateName();
- if (mWebViewCore.getSettings().getSaveFormData()
- && name != null) {
- Message update = mPrivateHandler.obtainMessage(
- REQUEST_FORM_DATA, nodePointer);
- RequestFormData updater = new RequestFormData(name,
- getUrl(), update);
- Thread t = new Thread(updater);
- t.start();
- }
- }
- mWebTextView.setMaxLength(maxLength);
- AutoCompleteAdapter adapter = null;
- mWebTextView.setAdapterCustom(adapter);
- mWebTextView.setSingleLine(isTextField);
- mWebTextView.setInPassword(nativeFocusCandidateIsPassword());
+ mWebTextView.setType(nativeFocusCandidateType());
if (null == text) {
- mWebTextView.setText("", 0, 0);
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "rebuildWebTextView null == text");
}
- } else {
- // Change to true to enable the old style behavior, where
- // entering a textfield/textarea always set the selection to the
- // whole field. This was desirable for the case where the user
- // intends to scroll past the field using the trackball.
- // However, it causes a problem when replying to emails - the
- // user expects the cursor to be at the beginning of the
- // textarea. Testing out a new behavior, where textfields set
- // selection at the end, and textareas at the beginning.
- if (false) {
- mWebTextView.setText(text, 0, text.length());
- } else if (isTextField) {
- int length = text.length();
- mWebTextView.setText(text, length, length);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "rebuildWebTextView length=" + length);
+ text = "";
+ }
+ mWebTextView.setTextAndKeepSelection(text);
+ }
+ mWebTextView.requestFocus();
+ }
+
+ /**
+ * Called by WebTextView to find saved form data associated with the
+ * textfield
+ * @param name Name of the textfield.
+ * @param nodePointer Pointer to the node of the textfield, so it can be
+ * compared to the currently focused textfield when the data is
+ * retrieved.
+ */
+ /* package */ void requestFormData(String name, int nodePointer) {
+ if (mWebViewCore.getSettings().getSaveFormData()) {
+ Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
+ update.arg1 = nodePointer;
+ RequestFormData updater = new RequestFormData(name, getUrl(),
+ update);
+ Thread t = new Thread(updater);
+ t.start();
+ }
+ }
+
+ /**
+ * Pass a message to find out the <label> associated with the <input>
+ * identified by nodePointer
+ * @param framePointer Pointer to the frame containing the <input> node
+ * @param nodePointer Pointer to the node for which a <label> is desired.
+ */
+ /* package */ void requestLabel(int framePointer, int nodePointer) {
+ mWebViewCore.sendMessage(EventHub.REQUEST_LABEL, framePointer,
+ nodePointer);
+ }
+
+ /*
+ * This class runs the layers animations in their own thread,
+ * so that we do not slow down the UI.
+ */
+ private class EvaluateLayersAnimations extends Thread {
+ boolean mRunning = true;
+ // delay corresponds to 40fps, no need to go faster.
+ int mDelay = 25; // in ms
+ public void run() {
+ while (mRunning) {
+ if (mLayersHaveAnimations && mRootLayer != 0) {
+ // updates is a C++ pointer to a Vector of AnimationValues
+ int updates = nativeEvaluateLayersAnimations(mRootLayer);
+ if (updates == 0) {
+ mRunning = false;
}
+ Message.obtain(mPrivateHandler,
+ WebView.IMMEDIATE_REPAINT_MSG_ID,
+ updates, 0).sendToTarget();
} else {
- mWebTextView.setText(text, 0, 0);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "rebuildWebTextView !isTextField");
- }
+ mRunning = false;
+ }
+ try {
+ Thread.currentThread().sleep(mDelay);
+ } catch (InterruptedException e) {
+ mRunning = false;
}
}
- mWebTextView.requestFocus();
+ }
+ public void cancel() {
+ mRunning = false;
}
}
@@ -3216,6 +3384,35 @@ public class WebView extends AbsoluteLayout
}
}
+ /**
+ * Dump the display tree to "/sdcard/displayTree.txt"
+ *
+ * @hide debug only
+ */
+ public void dumpDisplayTree() {
+ nativeDumpDisplayTree(getUrl());
+ }
+
+ /**
+ * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to
+ * "/sdcard/domTree.txt"
+ *
+ * @hide debug only
+ */
+ public void dumpDomTree(boolean toFile) {
+ mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0);
+ }
+
+ /**
+ * Dump the render tree to adb shell if "toFile" is False, otherwise dump it
+ * to "/sdcard/renderTree.txt"
+ *
+ * @hide debug only
+ */
+ public void dumpRenderTree(boolean toFile) {
+ mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0);
+ }
+
// This is used to determine long press with the center key. Does not
// affect long press with the trackball/touch.
private boolean mGotCenterDown = false;
@@ -3251,23 +3448,22 @@ public class WebView extends AbsoluteLayout
if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false
&& (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
- mExtendSelection = false;
- mShiftIsPressed = true;
- if (nativeHasCursorNode()) {
- Rect rect = nativeCursorNodeBounds();
- mSelectX = contentToViewX(rect.left);
- mSelectY = contentToViewY(rect.top);
- } else {
- mSelectX = mScrollX + (int) mLastTouchX;
- mSelectY = mScrollY + (int) mLastTouchY;
- }
- nativeHideCursor();
- }
+ setUpSelectXY();
+ }
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
// always handle the navigation keys in the UI thread
switchOutDrawHistory();
+ if (mShiftIsPressed) {
+ int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+ ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
+ int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
+ -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0;
+ int multiplier = event.getRepeatCount() + 1;
+ moveSelection(xRate * multiplier, yRate * multiplier);
+ return true;
+ }
if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) {
playSoundEffect(keyCodeToSoundsEffect(keyCode));
return true;
@@ -3279,6 +3475,9 @@ public class WebView extends AbsoluteLayout
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
switchOutDrawHistory();
if (event.getRepeatCount() == 0) {
+ if (mShiftIsPressed) {
+ return true; // discard press if copy in progress
+ }
mGotCenterDown = true;
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
@@ -3303,24 +3502,15 @@ public class WebView extends AbsoluteLayout
if (getSettings().getNavDump()) {
switch (keyCode) {
case KeyEvent.KEYCODE_4:
- // "/data/data/com.android.browser/displayTree.txt"
- nativeDumpDisplayTree(getUrl());
+ dumpDisplayTree();
break;
case KeyEvent.KEYCODE_5:
case KeyEvent.KEYCODE_6:
- // 5: dump the dom tree to the file
- // "/data/data/com.android.browser/domTree.txt"
- // 6: dump the dom tree to the adb log
- mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
- (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
+ dumpDomTree(keyCode == KeyEvent.KEYCODE_5);
break;
case KeyEvent.KEYCODE_7:
case KeyEvent.KEYCODE_8:
- // 7: dump the render tree to the file
- // "/data/data/com.android.browser/renderTree.txt"
- // 8: dump the render tree to the adb log
- mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
- (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
+ dumpRenderTree(keyCode == KeyEvent.KEYCODE_7);
break;
case KeyEvent.KEYCODE_9:
nativeInstrumentReport();
@@ -3328,10 +3518,7 @@ public class WebView extends AbsoluteLayout
}
}
- if (nativeCursorIsPlugin()) {
- nativeUpdatePluginReceivesEvents();
- invalidate();
- } else if (nativeCursorIsTextInput()) {
+ if (nativeCursorIsTextInput()) {
// This message will put the node in focus, for the DOM's notion
// of focus, and make the focuscontroller active
mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
@@ -3340,13 +3527,16 @@ public class WebView extends AbsoluteLayout
// our view system's notion of focus
rebuildWebTextView();
// Now we need to pass the event to it
- return mWebTextView.onKeyDown(keyCode, event);
+ if (inEditingMode()) {
+ mWebTextView.setDefaultSelection();
+ return mWebTextView.dispatchKeyEvent(event);
+ }
} else if (nativeHasFocusNode()) {
// In this case, the cursor is not on a text input, but the focus
// might be. Check it, and if so, hand over to the WebTextView.
rebuildWebTextView();
if (inEditingMode()) {
- return mWebTextView.onKeyDown(keyCode, event);
+ return mWebTextView.dispatchKeyEvent(event);
}
}
@@ -3411,7 +3601,13 @@ public class WebView extends AbsoluteLayout
mGotCenterDown = false;
if (mShiftIsPressed) {
- return false;
+ if (mExtendSelection) {
+ commitCopy();
+ } else {
+ mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
+ }
+ return true; // discard press if copy in progress
}
// perform the single click
@@ -3421,21 +3617,22 @@ public class WebView extends AbsoluteLayout
if (!nativeCursorIntersects(visibleRect)) {
return false;
}
- nativeSetFollowedLink(true);
- nativeUpdatePluginReceivesEvents();
WebViewCore.CursorData data = cursorData();
mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
playSoundEffect(SoundEffectConstants.CLICK);
- boolean isTextInput = nativeCursorIsTextInput();
- if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading(
- nativeCursorText())) {
+ if (nativeCursorIsTextInput()) {
+ rebuildWebTextView();
+ centerKeyPressOnTextField();
+ if (inEditingMode()) {
+ mWebTextView.setDefaultSelection();
+ }
+ return true;
+ }
+ nativeSetFollowedLink(true);
+ if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
nativeCursorNodePointer());
}
- if (isTextInput) {
- rebuildWebTextView();
- displaySoftKeyboard(true);
- }
return true;
}
@@ -3451,70 +3648,85 @@ public class WebView extends AbsoluteLayout
return false;
}
- /**
- * @hide
- */
- public void emulateShiftHeld() {
- if (0 == mNativeClass) return; // client isn't initialized
+ private void setUpSelectXY() {
mExtendSelection = false;
mShiftIsPressed = true;
+ if (nativeHasCursorNode()) {
+ Rect rect = nativeCursorNodeBounds();
+ mSelectX = contentToViewX(rect.left);
+ mSelectY = contentToViewY(rect.top);
+ } else if (mLastTouchY > getVisibleTitleHeight()) {
+ mSelectX = mScrollX + (int) mLastTouchX;
+ mSelectY = mScrollY + (int) mLastTouchY;
+ } else {
+ mSelectX = mScrollX + getViewWidth() / 2;
+ mSelectY = mScrollY + getViewHeightWithTitle() / 2;
+ }
nativeHideCursor();
}
+ public void emulateShiftHeld() {
+ if (0 == mNativeClass) return; // client isn't initialized
+ setUpSelectXY();
+ }
+
private boolean commitCopy() {
boolean copiedSomething = false;
if (mExtendSelection) {
- // copy region so core operates on copy without touching orig.
- Region selection = new Region(nativeGetSelection());
- if (selection.isEmpty() == false) {
+ String selection = nativeGetSelection();
+ if (selection != "") {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "commitCopy \"" + selection + "\"");
+ }
Toast.makeText(mContext
, com.android.internal.R.string.text_copied
, Toast.LENGTH_SHORT).show();
- mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
copiedSomething = true;
+ try {
+ IClipboard clip = IClipboard.Stub.asInterface(
+ ServiceManager.getService("clipboard"));
+ clip.setClipboardText(selection);
+ } catch (android.os.RemoteException e) {
+ Log.e(LOGTAG, "Clipboard failed", e);
+ }
}
mExtendSelection = false;
}
mShiftIsPressed = false;
+ invalidate(); // remove selection region and pointer
if (mTouchMode == TOUCH_SELECT_MODE) {
mTouchMode = TOUCH_INIT_MODE;
}
return copiedSomething;
}
- // Set this as a hierarchy change listener so we can know when this view
- // is removed and still have access to our parent.
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- ViewParent parent = getParent();
- if (parent instanceof ViewGroup) {
- ViewGroup p = (ViewGroup) parent;
- p.setOnHierarchyChangeListener(this);
- }
+ if (hasWindowFocus()) onWindowFocusChanged(true);
}
@Override
protected void onDetachedFromWindow() {
+ clearTextEntry();
super.onDetachedFromWindow();
- ViewParent parent = getParent();
- if (parent instanceof ViewGroup) {
- ViewGroup p = (ViewGroup) parent;
- p.setOnHierarchyChangeListener(null);
- }
-
// Clean up the zoom controller
mZoomButtonsController.setVisible(false);
}
- // Implementation for OnHierarchyChangeListener
+ /**
+ * @deprecated WebView no longer needs to implement
+ * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
+ */
+ @Deprecated
public void onChildViewAdded(View parent, View child) {}
- public void onChildViewRemoved(View p, View child) {
- if (child == this) {
- clearTextEntry();
- }
- }
+ /**
+ * @deprecated WebView no longer needs to implement
+ * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
+ */
+ @Deprecated
+ public void onChildViewRemoved(View p, View child) {}
/**
* @deprecated WebView should not have implemented
@@ -3619,6 +3831,24 @@ public class WebView extends AbsoluteLayout
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
+ /**
+ * @hide
+ */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean changed = super.setFrame(left, top, right, bottom);
+ if (!changed && mHeightCanMeasure) {
+ // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
+ // in WebViewCore after we get the first layout. We do call
+ // requestLayout() when we get contentSizeChanged(). But the View
+ // system won't call onSizeChanged if the dimension is not changed.
+ // In this case, we need to call sendViewSizeZoom() explicitly to
+ // notify the WebKit about the new dimensions.
+ sendViewSizeZoom();
+ }
+ return changed;
+ }
+
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
@@ -3626,8 +3856,14 @@ public class WebView extends AbsoluteLayout
if (mZoomScale == 0) { // unless we're already zooming
mZoomCenterX = getViewWidth() * .5f;
mZoomCenterY = getViewHeight() * .5f;
- mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
- mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+ }
+
+ // adjust the max viewport width depending on the view dimensions. This
+ // is to ensure the scaling is not going insane. So do not shrink it if
+ // the view size is temporarily smaller, e.g. when soft keyboard is up.
+ int newMaxViewportWidth = (int) (Math.max(w, h) / DEFAULT_MIN_ZOOM_SCALE);
+ if (newMaxViewportWidth > sMaxViewportWidth) {
+ sMaxViewportWidth = newMaxViewportWidth;
}
// update mMinZoomScale if the minimum zoom scale is not fixed
@@ -3648,10 +3884,19 @@ public class WebView extends AbsoluteLayout
}
}
- // we always force, in case our height changed, in which case we still
- // want to send the notification over to webkit
- // only update the text wrap scale if width changed.
- setNewZoomScale(mActualScale, w != ow, true);
+ // onSizeChanged() is called during WebView layout. And any
+ // requestLayout() is blocked during layout. As setNewZoomScale() will
+ // call its child View to reposition itself through ViewManager's
+ // scaleAll(), we need to post a Runnable to ensure requestLayout().
+ post(new Runnable() {
+ public void run() {
+ // we always force, in case our height changed, in which case we
+ // still want to send the notification over to webkit
+ if (mWebViewCore != null) {
+ setNewZoomScale(mActualScale, true);
+ }
+ }
+ });
}
@Override
@@ -3699,97 +3944,166 @@ public class WebView extends AbsoluteLayout
private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
- // MultiTouch handling
- private static boolean mSupportMultiTouch;
+ private static int sign(float x) {
+ return x > 0 ? 1 : (x < 0 ? -1 : 0);
+ }
- private double mPinchDistance;
- private float mLastPressure;
- private int mAnchorX;
- private int mAnchorY;
+ // if the page can scroll <= this value, we won't allow the drag tracker
+ // to have any effect.
+ private static final int MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER = 4;
- private static float SCALE_INCREMENT = 0.01f;
- private static float PRESSURE_THRESHOLD = 0.67f;
+ private class DragTrackerHandler {
+ private final DragTracker mProxy;
+ private final float mStartY, mStartX;
+ private final float mMinDY, mMinDX;
+ private final float mMaxDY, mMaxDX;
+ private float mCurrStretchY, mCurrStretchX;
+ private int mSX, mSY;
- private boolean doMultiTouch(MotionEvent ev) {
- int action = ev.getAction();
+ public DragTrackerHandler(float x, float y, DragTracker proxy) {
+ mProxy = proxy;
+
+ int docBottom = computeVerticalScrollRange() + getTitleHeight();
+ int viewTop = getScrollY();
+ int viewBottom = viewTop + getHeight();
+
+ mStartY = y;
+ mMinDY = -viewTop;
+ mMaxDY = docBottom - viewBottom;
- if ((action & 0xff) == MotionEvent.ACTION_POINTER_DOWN) {
- // cancel the single touch handling
- cancelTouch();
- // reset the zoom overview mode so that the page won't auto grow
- mInZoomOverview = false;
- // If it is in password mode, turn it off so it does not draw
- // misplaced.
- if (inEditingMode() && nativeFocusCandidateIsPassword()) {
- mWebTextView.setInPassword(false);
+ if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+ Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " dragtracker y= " + y +
+ " up/down= " + mMinDY + " " + mMaxDY);
}
- // start multi (2-pointer) touch
- float x0 = ev.getX(0);
- float y0 = ev.getY(0);
- float x1 = ev.getX(1);
- float y1 = ev.getY(1);
- mPinchDistance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
- * (y0 - y1));
- } else if ((action & 0xff) == MotionEvent.ACTION_POINTER_UP) {
- if (mPreviewZoomOnly) {
- mPreviewZoomOnly = false;
- mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
- mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
- // for testing only, default don't reflow now
- boolean reflowNow = !getSettings().getPluginsEnabled();
- // force zoom after mPreviewZoomOnly is set to false so that the
- // new view size will be passed to the WebKit
- setNewZoomScale(mActualScale, reflowNow, true);
- // call invalidate() to draw without zoom filter
- invalidate();
+
+ int docRight = computeHorizontalScrollRange();
+ int viewLeft = getScrollX();
+ int viewRight = viewLeft + getWidth();
+ mStartX = x;
+ mMinDX = -viewLeft;
+ mMaxDX = docRight - viewRight;
+
+ mProxy.onStartDrag(x, y);
+
+ // ensure we buildBitmap at least once
+ mSX = -99999;
+ }
+
+ private float computeStretch(float delta, float min, float max) {
+ float stretch = 0;
+ if (max - min > MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER) {
+ if (delta < min) {
+ stretch = delta - min;
+ } else if (delta > max) {
+ stretch = delta - max;
+ }
}
- // adjust the edit text view if needed
- if (inEditingMode()) {
- adjustTextView(true);
+ return stretch;
+ }
+
+ public void dragTo(float x, float y) {
+ float sy = computeStretch(mStartY - y, mMinDY, mMaxDY);
+ float sx = computeStretch(mStartX - x, mMinDX, mMaxDX);
+
+ if (mCurrStretchX != sx || mCurrStretchY != sy) {
+ mCurrStretchX = sx;
+ mCurrStretchY = sy;
+ if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+ Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "---- stretch " + sx +
+ " " + sy);
+ }
+ if (mProxy.onStretchChange(sx, sy)) {
+ invalidate();
+ }
}
- // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it
- // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it
- // may trigger the unwanted fling.
- mTouchMode = TOUCH_PINCH_DRAG;
- // action indicates which pointer is UP. Use the other one as drag's
- // starting position.
- int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
- >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
- startTouch(ev.getX(id), ev.getY(id), ev.getEventTime());
- } else if (action == MotionEvent.ACTION_MOVE) {
- float x0 = ev.getX(0);
- float y0 = ev.getY(0);
- float x1 = ev.getX(1);
- float y1 = ev.getY(1);
- double distance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
- * (y0 - y1));
- float scale = (float) (Math.round(distance / mPinchDistance
- * mActualScale * 100) / 100.0);
- float pressure = ev.getPressure(0) + ev.getPressure(1);
- if (Math.abs(scale - mActualScale) >= SCALE_INCREMENT
- && (!mPreviewZoomOnly
- || (pressure / mLastPressure) > PRESSURE_THRESHOLD)) {
- mPreviewZoomOnly = true;
- // limit the scale change per step
- if (scale > mActualScale) {
- scale = Math.min(scale, mActualScale * 1.25f);
- } else {
- scale = Math.max(scale, mActualScale * 0.8f);
+ }
+
+ public void stopDrag() {
+ if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+ Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag");
+ }
+ mProxy.onStopDrag();
+ }
+
+ private int hiddenHeightOfTitleBar() {
+ return getTitleHeight() - getVisibleTitleHeight();
+ }
+
+ // need a way to know if 565 or 8888 is the right config for
+ // capturing the display and giving it to the drag proxy
+ private Bitmap.Config offscreenBitmapConfig() {
+ // hard code 565 for now
+ return Bitmap.Config.RGB_565;
+ }
+
+ /* If the tracker draws, then this returns true, otherwise it will
+ return false, and draw nothing.
+ */
+ public boolean draw(Canvas canvas) {
+ if (mCurrStretchX != 0 || mCurrStretchY != 0) {
+ int sx = getScrollX();
+ int sy = getScrollY() - hiddenHeightOfTitleBar();
+
+ if (mSX != sx || mSY != sy) {
+ buildBitmap(sx, sy);
+ mSX = sx;
+ mSY = sy;
}
- mZoomCenterX = (x0 + x1) / 2;
- mZoomCenterY = (y0 + y1) / 2;
- setNewZoomScale(scale, false, false);
- invalidate();
- mPinchDistance = distance;
- mLastPressure = pressure;
+
+ int count = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.translate(sx, sy);
+ mProxy.onDraw(canvas);
+ canvas.restoreToCount(count);
+ return true;
+ }
+ if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+ Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " -- draw false " +
+ mCurrStretchX + " " + mCurrStretchY);
}
- } else {
- Log.w(LOGTAG, action + " should not happen during doMultiTouch");
return false;
}
- return true;
+
+ private void buildBitmap(int sx, int sy) {
+ int w = getWidth();
+ int h = getViewHeight();
+ Bitmap bm = Bitmap.createBitmap(w, h, offscreenBitmapConfig());
+ Canvas canvas = new Canvas(bm);
+ canvas.translate(-sx, -sy);
+ drawContent(canvas);
+
+ if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+ Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "--- buildBitmap " + sx +
+ " " + sy + " " + w + " " + h);
+ }
+ mProxy.onBitmapChange(bm);
+ }
+ }
+
+ /** @hide */
+ public static class DragTracker {
+ public void onStartDrag(float x, float y) {}
+ public boolean onStretchChange(float sx, float sy) {
+ // return true to have us inval the view
+ return false;
+ }
+ public void onStopDrag() {}
+ public void onBitmapChange(Bitmap bm) {}
+ public void onDraw(Canvas canvas) {}
+ }
+
+ /** @hide */
+ public DragTracker getDragTracker() {
+ return mDragTracker;
+ }
+
+ /** @hide */
+ public void setDragTracker(DragTracker tracker) {
+ mDragTracker = tracker;
}
+ private DragTracker mDragTracker;
+ private DragTrackerHandler mDragTrackerHandler;
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
@@ -3801,11 +4115,6 @@ public class WebView extends AbsoluteLayout
+ mTouchMode);
}
- if (mSupportMultiTouch && getSettings().supportZoom()
- && mMinZoomScale < mMaxZoomScale && ev.getPointerCount() > 1) {
- return doMultiTouch(ev);
- }
-
int action = ev.getAction();
float x = ev.getX();
float y = ev.getY();
@@ -3833,8 +4142,10 @@ public class WebView extends AbsoluteLayout
mLastSentTouchTime = eventTime;
}
- int deltaX = (int) (mLastTouchX - x);
- int deltaY = (int) (mLastTouchY - y);
+ float fDeltaX = mLastTouchX - x;
+ float fDeltaY = mLastTouchY - y;
+ int deltaX = (int) fDeltaX;
+ int deltaY = (int) fDeltaY;
switch (action) {
case MotionEvent.ACTION_DOWN: {
@@ -3856,6 +4167,7 @@ public class WebView extends AbsoluteLayout
nativeMoveSelection(viewToContentX(mSelectX),
viewToContentY(mSelectY), false);
mTouchSelection = mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
} else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
@@ -3866,10 +4178,11 @@ public class WebView extends AbsoluteLayout
// continue, mTouchMode should be still TOUCH_INIT_MODE
}
} else {
- mPreviewZoomOnly = false;
mTouchMode = TOUCH_INIT_MODE;
mPreventDrag = mForwardTouchEvents ? PREVENT_DRAG_MAYBE_YES
: PREVENT_DRAG_NO;
+ mPreventLongPress = false;
+ mPreventDoubleTap = false;
mWebViewCore.sendMessage(
EventHub.UPDATE_FRAME_CACHE_IF_LOADING);
if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
@@ -3884,7 +4197,15 @@ public class WebView extends AbsoluteLayout
.obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
}
// Remember where the motion event started
- startTouch(x, y, eventTime);
+ mLastTouchX = x;
+ mLastTouchY = y;
+ mLastTouchTime = eventTime;
+ mVelocityTracker = VelocityTracker.obtain();
+ mSnapScrollMode = SNAP_NONE;
+ if (mDragTracker != null) {
+ mDragTrackerHandler = new DragTrackerHandler(x, y,
+ mDragTracker);
+ }
break;
}
case MotionEvent.ACTION_MOVE: {
@@ -3922,6 +4243,11 @@ public class WebView extends AbsoluteLayout
|| mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
}
+ if (mFullScreenHolder != null) {
+ // in full screen mode, the WebView can't be panned.
+ mTouchMode = TOUCH_DONE_MODE;
+ break;
+ }
// if it starts nearly horizontal or vertical, enforce it
int ax = Math.abs(deltaX);
@@ -3935,36 +4261,50 @@ public class WebView extends AbsoluteLayout
}
mTouchMode = TOUCH_DRAG_MODE;
+ mLastTouchX = x;
+ mLastTouchY = y;
+ fDeltaX = 0.0f;
+ fDeltaY = 0.0f;
+ deltaX = 0;
+ deltaY = 0;
+
WebViewCore.pauseUpdate(mWebViewCore);
if (!mDragFromTextInput) {
nativeHideCursor();
}
- if (!mSupportMultiTouch) {
- WebSettings settings = getSettings();
- if (settings.supportZoom()
- && settings.getBuiltInZoomControls()
- && !mZoomButtonsController.isVisible()
- && mMinZoomScale < mMaxZoomScale) {
- mZoomButtonsController.setVisible(true);
- int count = settings.getDoubleTapToastCount();
- if (mInZoomOverview && count > 0) {
- settings.setDoubleTapToastCount(--count);
- Toast.makeText(mContext,
- com.android.internal.R.string.double_tap_toast,
- Toast.LENGTH_LONG).show();
- }
+ WebSettings settings = getSettings();
+ if (settings.supportZoom()
+ && settings.getBuiltInZoomControls()
+ && !mZoomButtonsController.isVisible()
+ && mMinZoomScale < mMaxZoomScale) {
+ mZoomButtonsController.setVisible(true);
+ int count = settings.getDoubleTapToastCount();
+ if (mInZoomOverview && count > 0) {
+ settings.setDoubleTapToastCount(--count);
+ Toast.makeText(mContext,
+ com.android.internal.R.string.double_tap_toast,
+ Toast.LENGTH_LONG).show();
}
}
}
// do pan
int newScrollX = pinLocX(mScrollX + deltaX);
- deltaX = newScrollX - mScrollX;
+ int newDeltaX = newScrollX - mScrollX;
+ if (deltaX != newDeltaX) {
+ deltaX = newDeltaX;
+ fDeltaX = (float) newDeltaX;
+ }
int newScrollY = pinLocY(mScrollY + deltaY);
- deltaY = newScrollY - mScrollY;
+ int newDeltaY = newScrollY - mScrollY;
+ if (deltaY != newDeltaY) {
+ deltaY = newDeltaY;
+ fDeltaY = (float) newDeltaY;
+ }
boolean done = false;
- if (deltaX == 0 && deltaY == 0) {
- done = true;
+ boolean keepScrollBarsVisible = false;
+ if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
+ keepScrollBarsVisible = done = true;
} else {
if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
int ax = Math.abs(deltaX);
@@ -3976,63 +4316,53 @@ public class WebView extends AbsoluteLayout
mSnapScrollMode = SNAP_NONE;
}
// reverse direction means lock in the snap mode
- if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
- ((mSnapPositive &&
- deltaX < -mMinLockSnapReverseDistance)
- || (!mSnapPositive &&
- deltaX > mMinLockSnapReverseDistance))) {
- mSnapScrollMode = SNAP_X_LOCK;
+ if (ax > MAX_SLOPE_FOR_DIAG * ay &&
+ (mSnapPositive
+ ? deltaX < -mMinLockSnapReverseDistance
+ : deltaX > mMinLockSnapReverseDistance)) {
+ mSnapScrollMode |= SNAP_LOCK;
}
} else {
// radical change means getting out of snap mode
- if ((ax > MAX_SLOPE_FOR_DIAG * ay)
+ if (ax > MAX_SLOPE_FOR_DIAG * ay
&& ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
mSnapScrollMode = SNAP_NONE;
}
// reverse direction means lock in the snap mode
- if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
- ((mSnapPositive &&
- deltaY < -mMinLockSnapReverseDistance)
- || (!mSnapPositive &&
- deltaY > mMinLockSnapReverseDistance))) {
- mSnapScrollMode = SNAP_Y_LOCK;
+ if (ay > MAX_SLOPE_FOR_DIAG * ax &&
+ (mSnapPositive
+ ? deltaY < -mMinLockSnapReverseDistance
+ : deltaY > mMinLockSnapReverseDistance)) {
+ mSnapScrollMode |= SNAP_LOCK;
}
}
}
-
- if (mSnapScrollMode == SNAP_X
- || mSnapScrollMode == SNAP_X_LOCK) {
- if (deltaX == 0) {
- // keep the scrollbar on the screen even there is no
- // scroll
- awakenScrollBars(ViewConfiguration
- .getScrollDefaultDelay(), false);
+ if (mSnapScrollMode != SNAP_NONE) {
+ if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
+ deltaY = 0;
} else {
- scrollBy(deltaX, 0);
+ deltaX = 0;
}
- mLastTouchX = x;
- } else if (mSnapScrollMode == SNAP_Y
- || mSnapScrollMode == SNAP_Y_LOCK) {
- if (deltaY == 0) {
- // keep the scrollbar on the screen even there is no
- // scroll
- awakenScrollBars(ViewConfiguration
- .getScrollDefaultDelay(), false);
- } else {
- scrollBy(0, deltaY);
+ }
+ if ((deltaX | deltaY) != 0) {
+ scrollBy(deltaX, deltaY);
+ if (deltaX != 0) {
+ mLastTouchX = x;
}
- mLastTouchY = y;
+ if (deltaY != 0) {
+ mLastTouchY = y;
+ }
+ mHeldMotionless = MOTIONLESS_FALSE;
} else {
- scrollBy(deltaX, deltaY);
- mLastTouchX = x;
- mLastTouchY = y;
+ // keep the scrollbar on the screen even there is no
+ // scroll
+ keepScrollBarsVisible = true;
}
mLastTouchTime = eventTime;
mUserScroll = true;
}
- if (!mSupportMultiTouch
- && !getSettings().getBuiltInZoomControls()) {
+ if (!getSettings().getBuiltInZoomControls()) {
boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
if (mZoomControls != null && showPlusMinus) {
if (mZoomControls.getVisibility() == View.VISIBLE) {
@@ -4045,23 +4375,44 @@ public class WebView extends AbsoluteLayout
}
}
- if (done) {
+ if (mDragTrackerHandler != null) {
+ mDragTrackerHandler.dragTo(x, y);
+ }
+
+ if (keepScrollBarsVisible) {
+ if (mHeldMotionless != MOTIONLESS_TRUE) {
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ }
// keep the scrollbar on the screen even there is no scroll
awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
false);
// return false to indicate that we can't pan out of the
// view space
- return false;
+ return !done;
}
break;
}
case MotionEvent.ACTION_UP: {
+ if (mDragTrackerHandler != null) {
+ mDragTrackerHandler.stopDrag();
+ mDragTrackerHandler = null;
+ }
mLastTouchUpTime = eventTime;
switch (mTouchMode) {
case TOUCH_DOUBLE_TAP_MODE: // double tap
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mTouchMode = TOUCH_DONE_MODE;
- doDoubleTap();
+ if (mPreventDoubleTap) {
+ WebViewCore.TouchEventData ted
+ = new WebViewCore.TouchEventData();
+ ted.mAction = WebViewCore.ACTION_DOUBLETAP;
+ ted.mX = viewToContentX((int) x + mScrollX);
+ ted.mY = viewToContentY((int) y + mScrollY);
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ } else if (mFullScreenHolder == null) {
+ doDoubleTap();
+ }
break;
case TOUCH_SELECT_MODE:
commitCopy();
@@ -4075,8 +4426,9 @@ public class WebView extends AbsoluteLayout
if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquare) {
Log.w(LOGTAG, "Miss a drag as we are waiting for" +
" WebCore's response for touch down.");
- if (computeHorizontalScrollExtent() < computeHorizontalScrollRange()
- || computeVerticalScrollExtent() < computeVerticalScrollRange()) {
+ if (mFullScreenHolder == null
+ && (computeHorizontalScrollExtent() < computeHorizontalScrollRange()
+ || computeVerticalScrollExtent() < computeVerticalScrollRange())) {
// we will not rewrite drag code here, but we
// will try fling if it applies.
WebViewCore.pauseUpdate(mWebViewCore);
@@ -4089,6 +4441,8 @@ public class WebView extends AbsoluteLayout
// if mPreventDrag is not confirmed, treat it as
// no so that it won't block tap or double tap.
mPreventDrag = PREVENT_DRAG_NO;
+ mPreventLongPress = false;
+ mPreventDoubleTap = false;
}
if (mPreventDrag == PREVENT_DRAG_NO) {
if (mTouchMode == TOUCH_INIT_MODE) {
@@ -4104,6 +4458,9 @@ public class WebView extends AbsoluteLayout
break;
}
case TOUCH_DRAG_MODE:
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_TRUE;
// redraw in high-quality, as we're done dragging
invalidate();
// if the user waits a while w/o moving before the
@@ -4131,38 +4488,33 @@ public class WebView extends AbsoluteLayout
break;
}
case MotionEvent.ACTION_CANCEL: {
- cancelTouch();
+ if (mDragTrackerHandler != null) {
+ mDragTrackerHandler.stopDrag();
+ mDragTrackerHandler = null;
+ }
+ // we also use mVelocityTracker == null to tell us that we are
+ // not "moving around", so we can take the slower/prettier
+ // mode in the drawing code
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ WebViewCore.resumeUpdate(mWebViewCore);
+ }
+ mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_TRUE;
+ mTouchMode = TOUCH_DONE_MODE;
+ nativeHideCursor();
break;
}
}
return true;
}
- private void startTouch(float x, float y, long eventTime) {
- mLastTouchX = x;
- mLastTouchY = y;
- mLastTouchTime = eventTime;
- mVelocityTracker = VelocityTracker.obtain();
- mSnapScrollMode = SNAP_NONE;
- }
-
- private void cancelTouch() {
- // we also use mVelocityTracker == null to tell us that we are not
- // "moving around", so we can take the slower/prettier mode in the
- // drawing code
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- if (mTouchMode == TOUCH_DRAG_MODE) {
- WebViewCore.resumeUpdate(mWebViewCore);
- }
- mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- mTouchMode = TOUCH_DONE_MODE;
- nativeHideCursor();
- }
-
private long mTrackballFirstTime = 0;
private long mTrackballLastTime = 0;
private float mTrackballRemainsX = 0.0f;
@@ -4181,6 +4533,7 @@ public class WebView extends AbsoluteLayout
private static final int SELECT_CURSOR_OFFSET = 16;
private int mSelectX = 0;
private int mSelectY = 0;
+ private boolean mFocusSizeChanged = false;
private boolean mShiftIsPressed = false;
private boolean mTrackballDown = false;
private long mTrackballUpTime = 0;
@@ -4239,6 +4592,7 @@ public class WebView extends AbsoluteLayout
commitCopy();
} else {
mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
}
return true; // discard press if copy in progress
}
@@ -4286,8 +4640,8 @@ public class WebView extends AbsoluteLayout
return;
int width = getViewWidth();
int height = getViewHeight();
- mSelectX += scaleTrackballX(xRate, width);
- mSelectY += scaleTrackballY(yRate, height);
+ mSelectX += xRate;
+ mSelectY += yRate;
int maxX = width + mScrollX;
int maxY = height + mScrollY;
mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
@@ -4369,8 +4723,11 @@ public class WebView extends AbsoluteLayout
}
float xRate = mTrackballRemainsX * 1000 / elapsed;
float yRate = mTrackballRemainsY * 1000 / elapsed;
+ int viewWidth = getViewWidth();
+ int viewHeight = getViewHeight();
if (mShiftIsPressed) {
- moveSelection(xRate, yRate);
+ moveSelection(scaleTrackballX(xRate, viewWidth),
+ scaleTrackballY(yRate, viewHeight));
mTrackballRemainsX = mTrackballRemainsY = 0;
return;
}
@@ -4384,8 +4741,8 @@ public class WebView extends AbsoluteLayout
+ " mTrackballRemainsX=" + mTrackballRemainsX
+ " mTrackballRemainsY=" + mTrackballRemainsY);
}
- int width = mContentWidth - getViewWidth();
- int height = mContentHeight - getViewHeight();
+ int width = mContentWidth - viewWidth;
+ int height = mContentHeight - viewHeight;
if (width < 0) width = 0;
if (height < 0) height = 0;
ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
@@ -4460,7 +4817,7 @@ public class WebView extends AbsoluteLayout
int vy = (int) mVelocityTracker.getYVelocity();
if (mSnapScrollMode != SNAP_NONE) {
- if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
+ if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
vy = 0;
} else {
vx = 0;
@@ -4522,7 +4879,7 @@ public class WebView extends AbsoluteLayout
scale = mDefaultScale;
}
- setNewZoomScale(scale, true, false);
+ setNewZoomScale(scale, false);
if (oldScale != mActualScale) {
// use mZoomPickerScale to see zoom preview first
@@ -4530,6 +4887,9 @@ public class WebView extends AbsoluteLayout
mInvInitialZoomScale = 1.0f / oldScale;
mInvFinalZoomScale = 1.0f / mActualScale;
mZoomScale = mActualScale;
+ if (!mInZoomOverview) {
+ mLastScale = scale;
+ }
invalidate();
return true;
} else {
@@ -4627,13 +4987,18 @@ public class WebView extends AbsoluteLayout
public boolean zoomIn() {
// TODO: alternatively we can disallow this during draw history mode
switchOutDrawHistory();
- mInZoomOverview = false;
// Center zooming to the center of the screen.
- mZoomCenterX = getViewWidth() * .5f;
- mZoomCenterY = getViewHeight() * .5f;
- mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
- mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
- return zoomWithPreview(mActualScale * 1.25f);
+ if (mInZoomOverview) {
+ // if in overview mode, bring it back to normal mode
+ mLastTouchX = getViewWidth() * .5f;
+ mLastTouchY = getViewHeight() * .5f;
+ doDoubleTap();
+ return true;
+ } else {
+ mZoomCenterX = getViewWidth() * .5f;
+ mZoomCenterY = getViewHeight() * .5f;
+ return zoomWithPreview(mActualScale * 1.25f);
+ }
}
/**
@@ -4643,12 +5008,20 @@ public class WebView extends AbsoluteLayout
public boolean zoomOut() {
// TODO: alternatively we can disallow this during draw history mode
switchOutDrawHistory();
- // Center zooming to the center of the screen.
- mZoomCenterX = getViewWidth() * .5f;
- mZoomCenterY = getViewHeight() * .5f;
- mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
- mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
- return zoomWithPreview(mActualScale * 0.8f);
+ float scale = mActualScale * 0.8f;
+ if (scale < (mMinZoomScale + 0.1f)
+ && mWebViewCore.getSettings().getUseWideViewPort()
+ && mZoomOverviewWidth > Math.ceil(getViewWidth()
+ * mInvActualScale)) {
+ // when zoom out to min scale, switch to overview mode
+ doDoubleTap();
+ return true;
+ } else {
+ // Center zooming to the center of the screen.
+ mZoomCenterX = getViewWidth() * .5f;
+ mZoomCenterY = getViewHeight() * .5f;
+ return zoomWithPreview(scale);
+ }
}
private void updateSelection() {
@@ -4716,7 +5089,7 @@ public class WebView extends AbsoluteLayout
}
/**
- * Do a touch up from a WebTextView. This will be handled by webkit to
+ * Due a touch up from a WebTextView. This will be handled by webkit to
* change the selection.
* @param event MotionEvent in the WebTextView's coordinates.
*/
@@ -4726,23 +5099,15 @@ public class WebView extends AbsoluteLayout
}
int x = viewToContentX((int) event.getX() + mWebTextView.getLeft());
int y = viewToContentY((int) event.getY() + mWebTextView.getTop());
- // In case the soft keyboard has been dismissed, bring it back up.
- InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView,
- 0);
- if (nativeFocusNodePointer() != nativeCursorNodePointer()) {
- nativeMotionUp(x, y, mNavSlop);
- }
- nativeTextInputMotionUp(x, y);
+ nativeMotionUp(x, y, mNavSlop);
}
- /*package*/ void shortPressOnTextField() {
- if (inEditingMode()) {
- View v = mWebTextView;
- int x = viewToContentX((v.getLeft() + v.getRight()) >> 1);
- int y = viewToContentY((v.getTop() + v.getBottom()) >> 1);
- displaySoftKeyboard(true);
- nativeTextInputMotionUp(x, y);
- }
+ /**
+ * Called when pressing the center key or trackball on a textfield.
+ */
+ /*package*/ void centerKeyPressOnTextField() {
+ mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
+ nativeCursorNodePointer());
}
private void doShortPress() {
@@ -4753,6 +5118,22 @@ public class WebView extends AbsoluteLayout
// mLastTouchX and mLastTouchY are the point in the current viewport
int contentX = viewToContentX((int) mLastTouchX + mScrollX);
int contentY = viewToContentY((int) mLastTouchY + mScrollY);
+ if (nativePointInNavCache(contentX, contentY, mNavSlop)) {
+ WebViewCore.MotionUpData motionUpData = new WebViewCore
+ .MotionUpData();
+ motionUpData.mFrame = nativeCacheHitFramePointer();
+ motionUpData.mNode = nativeCacheHitNodePointer();
+ motionUpData.mBounds = nativeCacheHitNodeBounds();
+ motionUpData.mX = contentX;
+ motionUpData.mY = contentY;
+ mWebViewCore.sendMessageAtFrontOfQueue(EventHub.VALID_NODE_BOUNDS,
+ motionUpData);
+ } else {
+ doMotionUp(contentX, contentY);
+ }
+ }
+
+ private void doMotionUp(int contentX, int contentY) {
if (nativeMotionUp(contentX, contentY, mNavSlop)) {
if (mLogEvent) {
Checkin.updateStats(mContext.getContentResolver(),
@@ -4764,71 +5145,50 @@ public class WebView extends AbsoluteLayout
}
}
- // Rule for double tap:
- // 1. if the current scale is not same as the text wrap scale and layout
- // algorithm is NARROW_COLUMNS, fit to column;
- // 2. if the current state is not overview mode, change to overview mode;
- // 3. if the current state is overview mode, change to default scale.
private void doDoubleTap() {
if (mWebViewCore.getSettings().getUseWideViewPort() == false) {
return;
}
mZoomCenterX = mLastTouchX;
mZoomCenterY = mLastTouchY;
- mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
- mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+ mInZoomOverview = !mInZoomOverview;
+ // remove the zoom control after double tap
WebSettings settings = getSettings();
- if (!mSupportMultiTouch) {
- // remove the zoom control after double tap
- if (settings.getBuiltInZoomControls()) {
- if (mZoomButtonsController.isVisible()) {
- mZoomButtonsController.setVisible(false);
- }
- } else {
- if (mZoomControlRunnable != null) {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- }
- if (mZoomControls != null) {
- mZoomControls.hide();
- }
+ if (settings.getBuiltInZoomControls()) {
+ if (mZoomButtonsController.isVisible()) {
+ mZoomButtonsController.setVisible(false);
}
- settings.setDoubleTapToastCount(0);
- }
- if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS)
- && (Math.abs(mActualScale - mTextWrapScale) >= 0.01f)) {
- setNewZoomScale(mActualScale, true, true);
- float overviewScale = (float) getViewWidth() / mZoomOverviewWidth;
- if (Math.abs(mActualScale - overviewScale) < 0.01f) {
- mInZoomOverview = true;
+ } else {
+ if (mZoomControlRunnable != null) {
+ mPrivateHandler.removeCallbacks(mZoomControlRunnable);
}
- } else if (!mInZoomOverview) {
+ if (mZoomControls != null) {
+ mZoomControls.hide();
+ }
+ }
+ settings.setDoubleTapToastCount(0);
+ if (mInZoomOverview) {
float newScale = (float) getViewWidth() / mZoomOverviewWidth;
- if (Math.abs(mActualScale - newScale) >= 0.01f) {
- mInZoomOverview = true;
+ if (Math.abs(mActualScale - newScale) < 0.01f) {
+ // reset mInZoomOverview to false if scale doesn't change
+ mInZoomOverview = false;
+ } else {
// Force the titlebar fully reveal in overview mode
if (mScrollY < getTitleHeight()) mScrollY = 0;
zoomWithPreview(newScale);
- } else if (Math.abs(mActualScale - mDefaultScale) >= 0.01f) {
- mInZoomOverview = true;
}
} else {
- mInZoomOverview = false;
- int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
+ // mLastTouchX and mLastTouchY are the point in the current viewport
+ int contentX = viewToContentX((int) mLastTouchX + mScrollX);
+ int contentY = viewToContentY((int) mLastTouchY + mScrollY);
+ int left = nativeGetBlockLeftEdge(contentX, contentY, mActualScale);
if (left != NO_LEFTEDGE) {
- // add a 5pt padding to the left edge.
- int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5))
- - mScrollX;
- // Re-calculate the zoom center so that the new scroll x will be
- // on the left edge.
- if (viewLeft > 0) {
- mZoomCenterX = viewLeft * mDefaultScale
- / (mDefaultScale - mActualScale);
- } else {
- scrollBy(viewLeft, 0);
- mZoomCenterX = 0;
- }
+ // add a 5pt padding to the left edge. Re-calculate the zoom
+ // center so that the new scroll x will be on the left edge.
+ mZoomCenterX = left < 5 ? 0 : (left - 5) * mLastScale
+ * mActualScale / (mLastScale - mActualScale);
}
- zoomWithPreview(mDefaultScale);
+ zoomWithPreview(mLastScale);
}
}
@@ -4838,15 +5198,6 @@ public class WebView extends AbsoluteLayout
mCallbackProxy.uiOverrideUrlLoading(url);
}
- // called by JNI
- private void sendPluginState(int state) {
- WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData();
- psd.mFrame = nativeCursorFramePointer();
- psd.mNode = nativeCursorNodePointer();
- psd.mState = state;
- mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd);
- }
-
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
boolean result = false;
@@ -4995,14 +5346,6 @@ public class WebView extends AbsoluteLayout
}
/* package */ void passToJavaScript(String currentText, KeyEvent event) {
- if (nativeCursorWantsKeyEvents() && !nativeCursorMatchesFocus()) {
- mWebViewCore.sendMessage(EventHub.CLICK);
- if (mWebTextView.mOkayForFocusNotToMatch) {
- int select = nativeFocusCandidateIsTextField() ?
- nativeFocusCandidateMaxLength() : 0;
- setSelection(select, select);
- }
- }
WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
arg.mEvent = event;
arg.mCurrentText = currentText;
@@ -5033,11 +5376,11 @@ public class WebView extends AbsoluteLayout
class PrivateHandler extends Handler {
@Override
public void handleMessage(Message msg) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD
- || msg.what > SHOW_RECT_MSG_ID ? Integer
- .toString(msg.what) : HandlerDebugString[msg.what
- - REMEMBER_PASSWORD]);
+ // exclude INVAL_RECT_MSG_ID since it is frequently output
+ if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
+ Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
+ > RETURN_LABEL ? Integer.toString(msg.what)
+ : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
}
if (mWebViewCore == null) {
// after WebView's destroy() is called, skip handling messages.
@@ -5063,9 +5406,13 @@ public class WebView extends AbsoluteLayout
// it won't block panning the page.
if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
mPreventDrag = PREVENT_DRAG_NO;
+ mPreventLongPress = false;
+ mPreventDoubleTap = false;
}
if (mTouchMode == TOUCH_INIT_MODE) {
- mTouchMode = TOUCH_SHORTPRESS_START_MODE;
+ mTouchMode = mFullScreenHolder == null
+ ? TOUCH_SHORTPRESS_START_MODE
+ : TOUCH_SHORTPRESS_MODE;
updateSelection();
} else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
mTouchMode = TOUCH_DONE_MODE;
@@ -5073,10 +5420,20 @@ public class WebView extends AbsoluteLayout
break;
}
case SWITCH_TO_LONGPRESS: {
- if (mPreventDrag == PREVENT_DRAG_NO) {
+ if (mPreventLongPress) {
mTouchMode = TOUCH_DONE_MODE;
- performLongClick();
- rebuildWebTextView();
+ WebViewCore.TouchEventData ted
+ = new WebViewCore.TouchEventData();
+ ted.mAction = WebViewCore.ACTION_LONGPRESS;
+ ted.mX = viewToContentX((int) mLastTouchX + mScrollX);
+ ted.mY = viewToContentY((int) mLastTouchY + mScrollY);
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ } else if (mPreventDrag == PREVENT_DRAG_NO) {
+ mTouchMode = TOUCH_DONE_MODE;
+ if (mFullScreenHolder == null) {
+ performLongClick();
+ rebuildWebTextView();
+ }
}
break;
}
@@ -5120,8 +5477,12 @@ public class WebView extends AbsoluteLayout
final Point viewSize = draw.mViewPoint;
boolean useWideViewport = settings.getUseWideViewPort();
WebViewCore.RestoreState restoreState = draw.mRestoreState;
- if (restoreState != null) {
+ boolean hasRestoreState = restoreState != null;
+ if (hasRestoreState) {
mInZoomOverview = false;
+ mLastScale = mInitialScaleInPercent > 0
+ ? mInitialScaleInPercent / 100.0f
+ : restoreState.mTextWrapScale;
if (restoreState.mMinScale == 0) {
if (restoreState.mMobileSite) {
if (draw.mMinPrefWidth >
@@ -5129,8 +5490,6 @@ public class WebView extends AbsoluteLayout
mMinZoomScale = (float) viewWidth
/ draw.mMinPrefWidth;
mMinZoomScaleFixed = false;
- mInZoomOverview = useWideViewport &&
- settings.getLoadWithOverviewMode();
} else {
mMinZoomScale = restoreState.mDefaultScale;
mMinZoomScaleFixed = true;
@@ -5148,32 +5507,26 @@ public class WebView extends AbsoluteLayout
} else {
mMaxZoomScale = restoreState.mMaxScale;
}
- if (mInitialScaleInPercent > 0) {
- setNewZoomScale(mInitialScaleInPercent / 100.0f,
- true, false);
- } else if (restoreState.mViewScale > 0) {
- mTextWrapScale = restoreState.mTextWrapScale;
- setNewZoomScale(restoreState.mViewScale, false,
- false);
- } else {
- mInZoomOverview = useWideViewport
- && settings.getLoadWithOverviewMode();
- if (mInZoomOverview) {
- setNewZoomScale((float) viewWidth
- / WebViewCore.DEFAULT_VIEWPORT_WIDTH,
- true, false);
- } else {
- setNewZoomScale(restoreState.mTextWrapScale,
- true, false);
- }
- }
+ setNewZoomScale(mLastScale, false);
setContentScrollTo(restoreState.mScrollX,
restoreState.mScrollY);
+ if (useWideViewport
+ && settings.getLoadWithOverviewMode()) {
+ if (restoreState.mViewScale == 0
+ || (restoreState.mMobileSite
+ && mMinZoomScale < restoreState.mDefaultScale)) {
+ mInZoomOverview = true;
+ }
+ }
// As we are on a new page, remove the WebTextView. This
// is necessary for page loads driven by webkit, and in
// particular when the user was on a password field, so
// the WebTextView was visible.
clearTextEntry();
+ // update the zoom buttons as the scale can be changed
+ if (getSettings().getBuiltInZoomControls()) {
+ updateZoomButtonsEnabled();
+ }
}
// We update the layout (i.e. request a layout from the
// view system) if the last view size that we sent to
@@ -5194,8 +5547,11 @@ public class WebView extends AbsoluteLayout
mPictureListener.onNewPicture(WebView.this, capturePicture());
}
if (useWideViewport) {
- mZoomOverviewWidth = Math.max(draw.mMinPrefWidth,
- draw.mViewPoint.x);
+ // limit mZoomOverviewWidth to sMaxViewportWidth so that
+ // if the page doesn't behave well, the WebView won't go
+ // insane.
+ mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math
+ .max(draw.mMinPrefWidth, draw.mViewPoint.x));
}
if (!mMinZoomScaleFixed) {
mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
@@ -5206,9 +5562,15 @@ public class WebView extends AbsoluteLayout
if (Math.abs((viewWidth * mInvActualScale)
- mZoomOverviewWidth) > 1) {
setNewZoomScale((float) viewWidth
- / mZoomOverviewWidth, true, false);
+ / mZoomOverviewWidth, false);
}
}
+ if (draw.mFocusSizeChanged && inEditingMode()) {
+ mFocusSizeChanged = true;
+ }
+ if (hasRestoreState) {
+ mViewManager.postReadyToDrawAll();
+ }
break;
}
case WEBCORE_INITIALIZED_MSG_ID:
@@ -5248,11 +5610,23 @@ public class WebView extends AbsoluteLayout
tData.mEnd);
}
break;
- case MOVE_OUT_OF_PLUGIN:
- if (nativePluginEatsNavKey()) {
- navHandledKey(msg.arg1, 1, false, 0, true);
+ case RETURN_LABEL:
+ if (inEditingMode()
+ && mWebTextView.isSameTextField(msg.arg1)) {
+ mWebTextView.setHint((String) msg.obj);
+ InputMethodManager imm
+ = InputMethodManager.peekInstance();
+ // The hint is propagated to the IME in
+ // onCreateInputConnection. If the IME is already
+ // active, restart it so that its hint text is updated.
+ if (imm != null && imm.isActive(mWebTextView)) {
+ imm.restartInput(mWebTextView);
+ }
}
break;
+ case MOVE_OUT_OF_PLUGIN:
+ navHandledKey(msg.arg1, 1, false, 0, true);
+ break;
case UPDATE_TEXT_ENTRY_MSG_ID:
// this is sent after finishing resize in WebViewCore. Make
// sure the text edit box is still on the screen.
@@ -5275,25 +5649,44 @@ public class WebView extends AbsoluteLayout
}
break;
}
+ case IMMEDIATE_REPAINT_MSG_ID: {
+ int updates = msg.arg1;
+ if (updates != 0) {
+ // updates is a C++ pointer to a Vector of
+ // AnimationValues that we apply to the layers.
+ // The Vector is deallocated in nativeUpdateLayers().
+ nativeUpdateLayers(mRootLayer, updates);
+ }
+ invalidate();
+ break;
+ }
+ case SET_ROOT_LAYER_MSG_ID: {
+ int oldLayer = mRootLayer;
+ mRootLayer = msg.arg1;
+ if (oldLayer > 0) {
+ nativeDestroyLayer(oldLayer);
+ }
+ if (mRootLayer == 0) {
+ mLayersHaveAnimations = false;
+ }
+ if (mEvaluateThread != null) {
+ mEvaluateThread.cancel();
+ mEvaluateThread = null;
+ }
+ if (nativeLayersHaveAnimations(mRootLayer)) {
+ mLayersHaveAnimations = true;
+ mEvaluateThread = new EvaluateLayersAnimations();
+ mEvaluateThread.start();
+ }
+ invalidate();
+ break;
+ }
case REQUEST_FORM_DATA:
AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj;
if (mWebTextView.isSameTextField(msg.arg1)) {
mWebTextView.setAdapterCustom(adapter);
}
break;
- case UPDATE_CLIPBOARD:
- String str = (String) msg.obj;
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str);
- }
- try {
- IClipboard clip = IClipboard.Stub.asInterface(
- ServiceManager.getService("clipboard"));
- clip.setClipboardText(str);
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Clipboard failed", e);
- }
- break;
case RESUME_WEBCORE_UPDATE:
WebViewCore.resumeUpdate(mWebViewCore);
break;
@@ -5321,10 +5714,18 @@ public class WebView extends AbsoluteLayout
// dont override if mPreventDrag has been set to no due
// to time out
if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
- mPreventDrag = msg.arg2 == 1 ? PREVENT_DRAG_YES
+ mPreventDrag = (msg.arg2 & TOUCH_PREVENT_DRAG)
+ == TOUCH_PREVENT_DRAG ? PREVENT_DRAG_YES
: PREVENT_DRAG_NO;
if (mPreventDrag == PREVENT_DRAG_YES) {
mTouchMode = TOUCH_DONE_MODE;
+ } else {
+ mPreventLongPress =
+ (msg.arg2 & TOUCH_PREVENT_LONGPRESS)
+ == TOUCH_PREVENT_LONGPRESS;
+ mPreventDoubleTap =
+ (msg.arg2 & TOUCH_PREVENT_DOUBLETAP)
+ == TOUCH_PREVENT_DOUBLETAP;
}
}
}
@@ -5334,45 +5735,108 @@ public class WebView extends AbsoluteLayout
if (msg.arg1 == 0) {
hideSoftKeyboard();
} else {
- displaySoftKeyboard(false);
+ displaySoftKeyboard(1 == msg.arg2);
}
break;
- case SHOW_RECT_MSG_ID:
- WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
- int x = mScrollX;
- int left = contentToViewDimension(data.mLeft);
- int width = contentToViewDimension(data.mWidth);
- int maxWidth = contentToViewDimension(data.mContentWidth);
- int viewWidth = getViewWidth();
- if (width < viewWidth) {
- // center align
- x += left + width / 2 - mScrollX - viewWidth / 2;
- } else {
- x += (int) (left + data.mXPercentInDoc * width
- - mScrollX - data.mXPercentInView * viewWidth);
+ case FIND_AGAIN:
+ // Ignore if find has been dismissed.
+ if (mFindIsUp) {
+ findAll(mLastFind);
+ }
+ break;
+
+ case DRAG_HELD_MOTIONLESS:
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ // fall through to keep scrollbars awake
+
+ case AWAKEN_SCROLL_BARS:
+ if (mTouchMode == TOUCH_DRAG_MODE
+ && mHeldMotionless == MOTIONLESS_TRUE) {
+ awakenScrollBars(ViewConfiguration
+ .getScrollDefaultDelay(), false);
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(AWAKEN_SCROLL_BARS),
+ ViewConfiguration.getScrollDefaultDelay());
+ }
+ break;
+
+ case DO_MOTION_UP:
+ doMotionUp(msg.arg1, msg.arg2);
+ break;
+
+ case SHOW_FULLSCREEN:
+ WebViewCore.PluginFullScreenData data
+ = (WebViewCore.PluginFullScreenData) msg.obj;
+ if (data.mNpp != 0 && data.mView != null) {
+ if (mFullScreenHolder != null) {
+ Log.w(LOGTAG,
+ "Should not have another full screen.");
+ mFullScreenHolder.dismiss();
+ }
+ mFullScreenHolder = new PluginFullScreenHolder(
+ WebView.this, data.mNpp);
+ mFullScreenHolder.setContentView(data.mView);
+ mFullScreenHolder.setCancelable(false);
+ mFullScreenHolder.setCanceledOnTouchOutside(false);
+ mFullScreenHolder.show();
}
- // use the passing content width to cap x as the current
- // mContentWidth may not be updated yet
- x = Math.max(0,
- (Math.min(maxWidth, x + viewWidth)) - viewWidth);
- int y = mScrollY;
- int top = contentToViewDimension(data.mTop);
- int height = contentToViewDimension(data.mHeight);
- int maxHeight = contentToViewDimension(data.mContentHeight);
+ // move the matching embedded view fully into the view so
+ // that touch will be valid instead of rejected due to out
+ // of the visible bounds
+ // TODO: do we need to preserve the original position and
+ // scale so that we can revert it when leaving the full
+ // screen mode?
+ int x = contentToViewX(data.mDocX);
+ int y = contentToViewY(data.mDocY);
+ int width = contentToViewDimension(data.mDocWidth);
+ int height = contentToViewDimension(data.mDocHeight);
+ int viewWidth = getViewWidth();
int viewHeight = getViewHeight();
- if (height < viewHeight) {
- // middle align
- y += top + height / 2 - mScrollY - viewHeight / 2;
- } else {
- y += (int) (top + data.mYPercentInDoc * height
- - mScrollY - data.mYPercentInView * viewHeight);
+ int newX = mScrollX;
+ int newY = mScrollY;
+ if (x < mScrollX) {
+ newX = x + (width > viewWidth
+ ? (width - viewWidth) / 2 : 0);
+ } else if (x + width > mScrollX + viewWidth) {
+ newX = x + width - viewWidth - (width > viewWidth
+ ? (width - viewWidth) / 2 : 0);
+ }
+ if (y < mScrollY) {
+ newY = y + (height > viewHeight
+ ? (height - viewHeight) / 2 : 0);
+ } else if (y + height > mScrollY + viewHeight) {
+ newY = y + height - viewHeight - (height > viewHeight
+ ? (height - viewHeight) / 2 : 0);
+ }
+ scrollTo(newX, newY);
+ if (width > viewWidth || height > viewHeight) {
+ mZoomCenterX = viewWidth * .5f;
+ mZoomCenterY = viewHeight * .5f;
+ setNewZoomScale(mActualScale
+ / Math.max((float) width / viewWidth,
+ (float) height / viewHeight), false);
+ }
+ // Now update the bound
+ mFullScreenHolder.updateBound(contentToViewX(data.mDocX)
+ - mScrollX, contentToViewY(data.mDocY) - mScrollY,
+ contentToViewDimension(data.mDocWidth),
+ contentToViewDimension(data.mDocHeight));
+ break;
+
+ case HIDE_FULLSCREEN:
+ if (mFullScreenHolder != null) {
+ mFullScreenHolder.dismiss();
+ mFullScreenHolder = null;
+ }
+ break;
+
+ case DOM_FOCUS_CHANGED:
+ if (inEditingMode()) {
+ nativeClearCursor();
+ rebuildWebTextView();
}
- // use the passing content height to cap y as the current
- // mContentHeight may not be updated yet
- y = Math.max(0,
- (Math.min(maxHeight, y + viewHeight) - viewHeight));
- scrollTo(x, y);
break;
default:
@@ -5398,8 +5862,16 @@ public class WebView extends AbsoluteLayout
// Need these to provide stable ids to my ArrayAdapter,
// which normally does not have stable ids. (Bug 1250098)
private class Container extends Object {
+ /**
+ * Possible values for mEnabled. Keep in sync with OptionStatus in
+ * WebViewCore.cpp
+ */
+ final static int OPTGROUP = -1;
+ final static int OPTION_DISABLED = 0;
+ final static int OPTION_ENABLED = 1;
+
String mString;
- boolean mEnabled;
+ int mEnabled;
int mId;
public String toString() {
@@ -5420,6 +5892,54 @@ public class WebView extends AbsoluteLayout
}
@Override
+ public View getView(int position, View convertView,
+ ViewGroup parent) {
+ // Always pass in null so that we will get a new CheckedTextView
+ // Otherwise, an item which was previously used as an <optgroup>
+ // element (i.e. has no check), could get used as an <option>
+ // element, which needs a checkbox/radio, but it would not have
+ // one.
+ convertView = super.getView(position, null, parent);
+ Container c = item(position);
+ if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
+ // ListView does not draw dividers between disabled and
+ // enabled elements. Use a LinearLayout to provide dividers
+ LinearLayout layout = new LinearLayout(mContext);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ if (position > 0) {
+ View dividerTop = new View(mContext);
+ dividerTop.setBackgroundResource(
+ android.R.drawable.divider_horizontal_bright);
+ layout.addView(dividerTop);
+ }
+
+ if (Container.OPTGROUP == c.mEnabled) {
+ // Currently select_dialog_multichoice and
+ // select_dialog_singlechoice are CheckedTextViews. If
+ // that changes, the class cast will no longer be valid.
+ Assert.assertTrue(
+ convertView instanceof CheckedTextView);
+ ((CheckedTextView) convertView).setCheckMarkDrawable(
+ null);
+ } else {
+ // c.mEnabled == Container.OPTION_DISABLED
+ // Draw the disabled element in a disabled state.
+ convertView.setEnabled(false);
+ }
+
+ layout.addView(convertView);
+ if (position < getCount() - 1) {
+ View dividerBottom = new View(mContext);
+ dividerBottom.setBackgroundResource(
+ android.R.drawable.divider_horizontal_bright);
+ layout.addView(dividerBottom);
+ }
+ return layout;
+ }
+ return convertView;
+ }
+
+ @Override
public boolean hasStableIds() {
// AdapterView's onChanged method uses this to determine whether
// to restore the old state. Return false so that the old (out
@@ -5454,12 +5974,11 @@ public class WebView extends AbsoluteLayout
if (item == null) {
return false;
}
- return item.mEnabled;
+ return Container.OPTION_ENABLED == item.mEnabled;
}
}
- private InvokeListBox(String[] array,
- boolean[] enabled, int[] selected) {
+ private InvokeListBox(String[] array, int[] enabled, int[] selected) {
mMultiple = true;
mSelectedArray = selected;
@@ -5473,8 +5992,7 @@ public class WebView extends AbsoluteLayout
}
}
- private InvokeListBox(String[] array, boolean[] enabled, int
- selection) {
+ private InvokeListBox(String[] array, int[] enabled, int selection) {
mSelection = selection;
mMultiple = false;
@@ -5605,10 +6123,11 @@ public class WebView extends AbsoluteLayout
* Request a dropdown menu for a listbox with multiple selection.
*
* @param array Labels for the listbox.
- * @param enabledArray Which positions are enabled.
+ * @param enabledArray State for each element in the list. See static
+ * integers in Container class.
* @param selectedArray Which positions are initally selected.
*/
- void requestListBox(String[] array, boolean[]enabledArray, int[]
+ void requestListBox(String[] array, int[] enabledArray, int[]
selectedArray) {
mPrivateHandler.post(
new InvokeListBox(array, enabledArray, selectedArray));
@@ -5619,15 +6138,22 @@ public class WebView extends AbsoluteLayout
* <select> element.
*
* @param array Labels for the listbox.
- * @param enabledArray Which positions are enabled.
+ * @param enabledArray State for each element in the list. See static
+ * integers in Container class.
* @param selection Which position is initally selected.
*/
- void requestListBox(String[] array, boolean[]enabledArray, int selection) {
+ void requestListBox(String[] array, int[] enabledArray, int selection) {
mPrivateHandler.post(
new InvokeListBox(array, enabledArray, selection));
}
// called by JNI
+ private void sendMoveFocus(int frame, int node) {
+ mWebViewCore.sendMessage(EventHub.SET_MOVE_FOCUS,
+ new WebViewCore.CursorData(frame, node, 0, 0));
+ }
+
+ // called by JNI
private void sendMoveMouse(int frame, int node, int x, int y) {
mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE,
new WebViewCore.CursorData(frame, node, x, y));
@@ -5706,7 +6232,7 @@ public class WebView extends AbsoluteLayout
if (mNativeClass == 0) {
return false;
}
- if (ignorePlugin == false && nativePluginEatsNavKey()) {
+ if (ignorePlugin == false && nativeFocusIsPlugin()) {
KeyEvent event = new KeyEvent(time, time, KeyEvent.ACTION_DOWN
, keyCode, count, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0)
| (false ? KeyEvent.META_ALT_ON : 0) // FIXME
@@ -5781,6 +6307,17 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Draw the HTML page into the specified canvas. This call ignores any
+ * view-specific zoom, scroll offset, or other changes. It does not draw
+ * any view-specific chrome, such as progress or URL bars.
+ *
+ * @hide only needs to be accessible to Browser and testing
+ */
+ public void drawPage(Canvas canvas) {
+ mWebViewCore.drawContentPicture(canvas, 0, false, false);
+ }
+
+ /**
* Update our cache with updatedText.
* @param updatedText The new text to put in our cache.
*/
@@ -5790,15 +6327,17 @@ public class WebView extends AbsoluteLayout
nativeUpdateCachedTextfield(updatedText, mTextGeneration);
}
+ private native int nativeCacheHitFramePointer();
+ private native Rect nativeCacheHitNodeBounds();
+ private native int nativeCacheHitNodePointer();
/* package */ native void nativeClearCursor();
private native void nativeCreate(int ptr);
private native int nativeCursorFramePointer();
private native Rect nativeCursorNodeBounds();
- /* package */ native int nativeCursorNodePointer();
+ private native int nativeCursorNodePointer();
/* package */ native boolean nativeCursorMatchesFocus();
private native boolean nativeCursorIntersects(Rect visibleRect);
private native boolean nativeCursorIsAnchor();
- private native boolean nativeCursorIsPlugin();
private native boolean nativeCursorIsTextInput();
private native Point nativeCursorPosition();
private native String nativeCursorText();
@@ -5810,26 +6349,39 @@ public class WebView extends AbsoluteLayout
private native void nativeDebugDump();
private native void nativeDestroy();
private native void nativeDrawCursorRing(Canvas content);
+ private native void nativeDestroyLayer(int layer);
+ private native int nativeEvaluateLayersAnimations(int layer);
+ private native boolean nativeLayersHaveAnimations(int layer);
+ private native void nativeUpdateLayers(int layer, int updates);
+ private native void nativeDrawLayers(int layer,
+ float scrollX, float scrollY,
+ float scale, Canvas canvas);
private native void nativeDrawMatches(Canvas canvas);
- private native void nativeDrawSelection(Canvas content, float scale,
- int offset, int x, int y, boolean extendSelection);
+ private native void nativeDrawSelectionPointer(Canvas content,
+ float scale, int x, int y, boolean extendSelection);
private native void nativeDrawSelectionRegion(Canvas content);
private native void nativeDumpDisplayTree(String urlOrNull);
private native int nativeFindAll(String findLower, String findUpper);
private native void nativeFindNext(boolean forward);
+ /* package */ native int nativeFocusCandidateFramePointer();
private native boolean nativeFocusCandidateIsPassword();
private native boolean nativeFocusCandidateIsRtlText();
- private native boolean nativeFocusCandidateIsTextField();
private native boolean nativeFocusCandidateIsTextInput();
- private native int nativeFocusCandidateMaxLength();
+ /* package */ native int nativeFocusCandidateMaxLength();
/* package */ native String nativeFocusCandidateName();
private native Rect nativeFocusCandidateNodeBounds();
- /* package */ native int nativeFocusCandidatePointer();
+ private native int nativeFocusCandidatePointer();
private native String nativeFocusCandidateText();
private native int nativeFocusCandidateTextSize();
+ /**
+ * Returns an integer corresponding to WebView.cpp::type.
+ * See WebTextView.setType()
+ */
+ private native int nativeFocusCandidateType();
+ private native boolean nativeFocusIsPlugin();
/* package */ native int nativeFocusNodePointer();
private native Rect nativeGetCursorRingBounds();
- private native Region nativeGetSelection();
+ private native String nativeGetSelection();
private native boolean nativeHasCursorNode();
private native boolean nativeHasFocusNode();
private native void nativeHideCursor();
@@ -5844,29 +6396,22 @@ public class WebView extends AbsoluteLayout
private native int nativeMoveGeneration();
private native void nativeMoveSelection(int x, int y,
boolean extendSelection);
- private native boolean nativePluginEatsNavKey();
+ private native boolean nativePointInNavCache(int x, int y, int slop);
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
private native void nativeRecordButtons(boolean focused,
boolean pressed, boolean invalidate);
private native void nativeSelectBestAt(Rect rect);
- private native void nativeSetFindIsDown();
+ private native void nativeSetFindIsUp();
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
// Returns a value corresponding to CachedFrame::ImeAction
/* package */ native int nativeTextFieldAction();
- /**
- * Perform a click on a currently focused text input. Since it is already
- * focused, there is no need to go through the nativeMotionUp code, which
- * may change the Cursor.
- */
- private native void nativeTextInputMotionUp(int x, int y);
private native int nativeTextGeneration();
// Never call this version except by updateCachedTextfield(String) -
// we always want to pass in our generation number.
private native void nativeUpdateCachedTextfield(String updatedText,
int generation);
- private native void nativeUpdatePluginReceivesEvents();
// return NO_LEFTEDGE means failure.
private static final int NO_LEFTEDGE = -1;
private native int nativeGetBlockLeftEdge(int x, int y, float scale);
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 30dea74..02c7210 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -86,6 +86,8 @@ public class WebViewClient {
* @param view The WebView that is initiating the callback.
* @param cancelMsg The message to send if the host wants to cancel
* @param continueMsg The message to send if the host wants to continue
+ * @deprecated This method is no longer called. When the WebView encounters
+ * a redirect loop, it will cancel the load.
*/
public void onTooManyRedirects(WebView view, Message cancelMsg,
Message continueMsg) {
@@ -173,8 +175,6 @@ public class WebViewClient {
* @param handler An SslErrorHandler object that will handle the user's
* response.
* @param error The SSL error object.
- * @hide - hide this because it contains a parameter of type SslError,
- * which is located in a hidden package.
*/
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index c3817fb..949b318 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -18,6 +18,8 @@ package android.webkit;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.DrawFilter;
import android.graphics.Paint;
@@ -26,11 +28,13 @@ import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.provider.Browser;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
@@ -120,10 +124,6 @@ final class WebViewCore {
private int mWebkitScrollX = 0;
private int mWebkitScrollY = 0;
- // If the site doesn't use viewport meta tag to specify the viewport, use
- // DEFAULT_VIEWPORT_WIDTH as default viewport width
- static final int DEFAULT_VIEWPORT_WIDTH = 800;
-
// The thread name used to identify the WebCore thread and for use in
// debugging other classes that require operation within the WebCore thread.
/* package */ static final String THREAD_NAME = "WebViewCoreThread";
@@ -273,6 +273,39 @@ final class WebViewCore {
mCallbackProxy.onJsAlert(url, message);
}
+
+ /**
+ * Called by JNI. Open a file chooser to upload a file.
+ * @return String version of the URI plus the name of the file.
+ * FIXME: Just return the URI here, and in FileSystem::pathGetFileName, call
+ * into Java to get the filename.
+ */
+ private String openFileChooser() {
+ Uri uri = mCallbackProxy.openFileChooser();
+ if (uri == null) return "";
+ // Find out the name, and append it to the URI.
+ // Webkit will treat the name as the filename, and
+ // the URI as the path. The URI will be used
+ // in BrowserFrame to get the actual data.
+ Cursor cursor = mContext.getContentResolver().query(
+ uri,
+ new String[] { OpenableColumns.DISPLAY_NAME },
+ null,
+ null,
+ null);
+ String name = "";
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ name = cursor.getString(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return uri.toString() + "/" + name;
+ }
+
/**
* Notify the browser that the origin has exceeded it's database quota.
* @param url The URL that caused the overflow.
@@ -422,6 +455,8 @@ final class WebViewCore {
*/
private native boolean nativeRecordContent(Region invalRegion, Point wh);
+ private native boolean nativeFocusBoundsChanged();
+
/**
* Splits slow parts of the picture set. Called from the webkit
* thread after nativeDrawContent returns true.
@@ -447,8 +482,8 @@ final class WebViewCore {
should this be called nativeSetViewPortSize?
*/
private native void nativeSetSize(int width, int height, int screenWidth,
- float scale, int realScreenWidth, int screenHeight, int anchorX,
- int anchorY, boolean ignoreHeight);
+ float scale, int realScreenWidth, int screenHeight,
+ boolean ignoreHeight);
private native int nativeGetContentMinPrefWidth();
@@ -465,17 +500,19 @@ final class WebViewCore {
private native void nativeSaveDocumentState(int frame);
+ private native void nativeMoveFocus(int framePtr, int nodePointer);
private native void nativeMoveMouse(int framePtr, int x, int y);
private native void nativeMoveMouseIfLatest(int moveGeneration,
int framePtr, int x, int y);
private native String nativeRetrieveHref(int framePtr, int nodePtr);
+ private native String nativeRetrieveAnchorText(int framePtr, int nodePtr);
private native void nativeTouchUp(int touchGeneration,
int framePtr, int nodePtr, int x, int y);
- private native boolean nativeHandleTouchEvent(int action, int x, int y);
+ private native int nativeHandleTouchEvent(int action, int x, int y);
private native void nativeUpdateFrameCache();
@@ -508,8 +545,6 @@ final class WebViewCore {
*/
private native void nativeSetSelection(int start, int end);
- private native String nativeGetSelection(Region sel);
-
// Register a scheme to be treated as local scheme so that it can access
// local asset files for resources
private native void nativeRegisterURLSchemeAsLocal(String scheme);
@@ -522,8 +557,6 @@ final class WebViewCore {
*/
private native void nativeSetNewStorageLimit(long limit);
- private native void nativeUpdatePluginState(int framePtr, int nodePtr, int state);
-
/**
* Provide WebCore with a Geolocation permission state for the specified
* origin.
@@ -624,11 +657,13 @@ final class WebViewCore {
CursorData() {}
CursorData(int frame, int node, int x, int y) {
mFrame = frame;
+ mNode = node;
mX = x;
mY = y;
}
int mMoveGeneration;
int mFrame;
+ int mNode;
int mX;
int mY;
}
@@ -643,6 +678,14 @@ final class WebViewCore {
KeyEvent mEvent;
}
+ static class MotionUpData {
+ int mFrame;
+ int mNode;
+ Rect mBounds;
+ int mX;
+ int mY;
+ }
+
static class PostUrlData {
String mUrl;
byte[] mPostData;
@@ -672,25 +715,34 @@ final class WebViewCore {
int mY;
}
+ // mAction of TouchEventData can be MotionEvent.getAction() which uses the
+ // last two bytes or one of the following values
+ static final int ACTION_LONGPRESS = 0x100;
+ static final int ACTION_DOUBLETAP = 0x200;
+
static class TouchEventData {
- int mAction; // MotionEvent.getAction()
+ int mAction;
int mX;
int mY;
}
- static class PluginStateData {
- int mFrame;
- int mNode;
- int mState;
- }
-
static class GeolocationPermissionsData {
String mOrigin;
boolean mAllow;
boolean mRemember;
}
+ static class PluginFullScreenData {
+ View mView;
+ int mNpp;
+ int mDocX;
+ int mDocY;
+ int mDocWidth;
+ int mDocHeight;
+ }
+
static final String[] HandlerDebugString = {
+ "REQUEST_LABEL", // 97
"UPDATE_FRAME_CACHE_IF_LOADING", // = 98
"SCROLL_TEXT_INPUT", // = 99
"LOAD_URL", // = 100;
@@ -720,9 +772,9 @@ final class WebViewCore {
"SINGLE_LISTBOX_CHOICE", // = 124;
"MESSAGE_RELAY", // = 125;
"SET_BACKGROUND_COLOR", // = 126;
- "PLUGIN_STATE", // = 127;
+ "SET_MOVE_FOCUS", // = 127
"SAVE_DOCUMENT_STATE", // = 128;
- "GET_SELECTION", // = 129;
+ "129", // = 129;
"WEBKIT_DRAW", // = 130;
"SYNC_SCROLL", // = 131;
"POST_URL", // = 132;
@@ -739,10 +791,12 @@ final class WebViewCore {
"ON_PAUSE", // = 143
"ON_RESUME", // = 144
"FREE_MEMORY", // = 145
+ "VALID_NODE_BOUNDS", // = 146
};
class EventHub {
// Message Ids
+ static final int REQUEST_LABEL = 97;
static final int UPDATE_FRAME_CACHE_IF_LOADING = 98;
static final int SCROLL_TEXT_INPUT = 99;
static final int LOAD_URL = 100;
@@ -771,9 +825,9 @@ final class WebViewCore {
static final int SINGLE_LISTBOX_CHOICE = 124;
static final int MESSAGE_RELAY = 125;
static final int SET_BACKGROUND_COLOR = 126;
- static final int PLUGIN_STATE = 127; // plugin notifications
+ static final int SET_MOVE_FOCUS = 127;
static final int SAVE_DOCUMENT_STATE = 128;
- static final int GET_SELECTION = 129;
+
static final int WEBKIT_DRAW = 130;
static final int SYNC_SCROLL = 131;
static final int POST_URL = 132;
@@ -802,6 +856,7 @@ final class WebViewCore {
static final int ON_PAUSE = 143;
static final int ON_RESUME = 144;
static final int FREE_MEMORY = 145;
+ static final int VALID_NODE_BOUNDS = 146;
// Network-based messaging
static final int CLEAR_SSL_PREF_TABLE = 150;
@@ -821,6 +876,8 @@ final class WebViewCore {
static final int POPULATE_VISITED_LINKS = 181;
+ static final int HIDE_FULLSCREEN = 182;
+
// private message ids
private static final int DESTROY = 200;
@@ -852,11 +909,11 @@ final class WebViewCore {
@Override
public void handleMessage(Message msg) {
if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, (msg.what < UPDATE_FRAME_CACHE_IF_LOADING
+ Log.v(LOGTAG, (msg.what < REQUEST_LABEL
|| msg.what
- > FREE_MEMORY ? Integer.toString(msg.what)
+ > VALID_NODE_BOUNDS ? Integer.toString(msg.what)
: HandlerDebugString[msg.what
- - UPDATE_FRAME_CACHE_IF_LOADING])
+ - REQUEST_LABEL])
+ " arg1=" + msg.arg1 + " arg2=" + msg.arg2
+ " obj=" + msg.obj);
}
@@ -877,6 +934,19 @@ final class WebViewCore {
}
break;
+ case REQUEST_LABEL:
+ if (mWebView != null) {
+ int nodePointer = msg.arg2;
+ String label = nativeRequestLabel(msg.arg1,
+ nodePointer);
+ if (label != null && label.length() > 0) {
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.RETURN_LABEL, nodePointer,
+ 0, label).sendToTarget();
+ }
+ }
+ break;
+
case UPDATE_FRAME_CACHE_IF_LOADING:
nativeUpdateFrameCacheIfLoading();
break;
@@ -961,7 +1031,6 @@ final class WebViewCore {
(WebView.ViewSizeData) msg.obj;
viewSizeChanged(data.mWidth, data.mHeight,
data.mTextWrapWidth, data.mScale,
- data.mAnchorX, data.mAnchorY,
data.mIgnoreHeight);
break;
}
@@ -1032,11 +1101,6 @@ final class WebViewCore {
nativeFreeMemory();
break;
- case PLUGIN_STATE:
- PluginStateData psd = (PluginStateData) msg.obj;
- nativeUpdatePluginState(psd.mFrame, psd.mNode, psd.mState);
- break;
-
case SET_NETWORK_STATE:
if (BrowserFrame.sJavaBridge == null) {
throw new IllegalStateException("No WebView " +
@@ -1102,7 +1166,7 @@ final class WebViewCore {
mWebView.mPrivateHandler,
WebView.PREVENT_TOUCH_ID, ted.mAction,
nativeHandleTouchEvent(ted.mAction, ted.mX,
- ted.mY) ? 1 : 0).sendToTarget();
+ ted.mY)).sendToTarget();
break;
}
@@ -1125,6 +1189,11 @@ final class WebViewCore {
mBrowserFrame.documentAsText((Message) msg.obj);
break;
+ case SET_MOVE_FOCUS:
+ CursorData focusData = (CursorData) msg.obj;
+ nativeMoveFocus(focusData.mFrame, focusData.mNode);
+ break;
+
case SET_MOVE_MOUSE:
CursorData cursorData = (CursorData) msg.obj;
nativeMoveMouse(cursorData.mFrame,
@@ -1140,8 +1209,10 @@ final class WebViewCore {
case REQUEST_CURSOR_HREF: {
Message hrefMsg = (Message) msg.obj;
- String res = nativeRetrieveHref(msg.arg1, msg.arg2);
- hrefMsg.getData().putString("url", res);
+ hrefMsg.getData().putString("url",
+ nativeRetrieveHref(msg.arg1, msg.arg2));
+ hrefMsg.getData().putString("title",
+ nativeRetrieveAnchorText(msg.arg1, msg.arg2));
hrefMsg.sendToTarget();
break;
}
@@ -1193,13 +1264,6 @@ final class WebViewCore {
nativeSetBackgroundColor(msg.arg1);
break;
- case GET_SELECTION:
- String str = nativeGetSelection((Region) msg.obj);
- Message.obtain(mWebView.mPrivateHandler
- , WebView.UPDATE_CLIPBOARD, str)
- .sendToTarget();
- break;
-
case DUMP_DOMTREE:
nativeDumpDomTree(msg.arg1 == 1);
break;
@@ -1249,6 +1313,25 @@ final class WebViewCore {
case POPULATE_VISITED_LINKS:
nativeProvideVisitedHistory((String[])msg.obj);
break;
+
+ case VALID_NODE_BOUNDS: {
+ MotionUpData motionUpData = (MotionUpData) msg.obj;
+ if (!nativeValidNodeAndBounds(
+ motionUpData.mFrame, motionUpData.mNode,
+ motionUpData.mBounds)) {
+ nativeUpdateFrameCache();
+ }
+ Message message = mWebView.mPrivateHandler
+ .obtainMessage(WebView.DO_MOTION_UP,
+ motionUpData.mX, motionUpData.mY);
+ mWebView.mPrivateHandler.sendMessageAtFrontOfQueue(
+ message);
+ break;
+ }
+
+ case HIDE_FULLSCREEN:
+ nativeFullScreenPluginHidden(msg.arg1);
+ break;
}
}
};
@@ -1392,6 +1475,11 @@ final class WebViewCore {
mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2, obj));
}
+ void sendMessageAtFrontOfQueue(int what, Object obj) {
+ mEventHub.sendMessageAtFrontOfQueue(Message.obtain(
+ null, what, obj));
+ }
+
void sendMessageDelayed(int what, Object obj, long delay) {
mEventHub.sendMessageDelayed(Message.obtain(null, what, obj), delay);
}
@@ -1484,7 +1572,7 @@ final class WebViewCore {
// notify webkit that our virtual view size changed size (after inv-zoom)
private void viewSizeChanged(int w, int h, int textwrapWidth, float scale,
- int anchorX, int anchorY, boolean ignoreHeight) {
+ boolean ignoreHeight) {
if (DebugFlags.WEB_VIEW_CORE) {
Log.v(LOGTAG, "viewSizeChanged w=" + w + "; h=" + h
+ "; textwrapWidth=" + textwrapWidth + "; scale=" + scale);
@@ -1498,7 +1586,7 @@ final class WebViewCore {
if (mViewportWidth == -1) {
if (mSettings.getLayoutAlgorithm() ==
WebSettings.LayoutAlgorithm.NORMAL) {
- width = DEFAULT_VIEWPORT_WIDTH;
+ width = WebView.DEFAULT_VIEWPORT_WIDTH;
} else {
/*
* if a page's minimum preferred width is wider than the
@@ -1512,17 +1600,16 @@ final class WebViewCore {
* In the worse case, the native width will be adjusted when
* next zoom or screen orientation change happens.
*/
- width = Math.max(w, Math.max(DEFAULT_VIEWPORT_WIDTH,
- nativeGetContentMinPrefWidth()));
+ width = Math.min(WebView.sMaxViewportWidth, Math.max(w,
+ Math.max(WebView.DEFAULT_VIEWPORT_WIDTH,
+ nativeGetContentMinPrefWidth())));
}
- } else if (mViewportWidth > 0) {
- width = Math.max(w, mViewportWidth);
} else {
- width = Math.max(w, textwrapWidth);
+ width = Math.max(w, mViewportWidth);
}
}
nativeSetSize(width, width == w ? h : Math.round((float) width * h / w),
- textwrapWidth, scale, w, h, anchorX, anchorY, ignoreHeight);
+ textwrapWidth, scale, w, h, ignoreHeight);
// Remember the current width and height
boolean needInvalidate = (mCurrentViewWidth == 0);
mCurrentViewWidth = w;
@@ -1567,9 +1654,6 @@ final class WebViewCore {
// Used to avoid posting more than one split picture message.
private boolean mSplitPictureIsScheduled;
- // Used to suspend drawing.
- private boolean mDrawIsPaused;
-
// mRestoreState is set in didFirstLayout(), and reset in the next
// webkitDraw after passing it to the UI thread.
private RestoreState mRestoreState = null;
@@ -1596,6 +1680,7 @@ final class WebViewCore {
int mMinPrefWidth;
RestoreState mRestoreState; // only non-null if it is for the first
// picture set after the first layout
+ boolean mFocusSizeChanged;
}
private void webkitDraw() {
@@ -1610,10 +1695,11 @@ final class WebViewCore {
if (mWebView != null) {
// Send the native view size that was used during the most recent
// layout.
+ draw.mFocusSizeChanged = nativeFocusBoundsChanged();
draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
if (mSettings.getUseWideViewPort()) {
draw.mMinPrefWidth = Math.max(
- mViewportWidth == -1 ? DEFAULT_VIEWPORT_WIDTH
+ mViewportWidth == -1 ? WebView.DEFAULT_VIEWPORT_WIDTH
: (mViewportWidth == 0 ? mCurrentViewWidth
: mViewportWidth),
nativeGetContentMinPrefWidth());
@@ -1646,6 +1732,9 @@ final class WebViewCore {
final DrawFilter mZoomFilter =
new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
+ final DrawFilter mScrollFilter = null;
+ // If we need to trade more speed for less quality on slower devices
+ // use this: new PaintFlagsDrawFilter(SCROLL_BITS, 0);
/* package */ void drawContentPicture(Canvas canvas, int color,
boolean animatingZoom,
@@ -1654,7 +1743,7 @@ final class WebViewCore {
if (animatingZoom) {
df = mZoomFilter;
} else if (animatingScroll) {
- df = null;
+ df = mScrollFilter;
}
canvas.setDrawFilter(df);
boolean tookTooLong = nativeDrawContent(canvas, color);
@@ -1683,17 +1772,6 @@ final class WebViewCore {
sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
.obtainMessage(WebCoreThread.REDUCE_PRIORITY));
- // Note: there is one possible failure mode. If pauseUpdate() is called
- // from UI thread while in webcore thread WEBKIT_DRAW is just pulled out
- // of the queue and about to be executed. mDrawIsScheduled may be set to
- // false in webkitDraw(). So update won't be blocked. But at least the
- // webcore thread priority is still lowered.
- if (core != null) {
- synchronized (core) {
- core.mDrawIsPaused = true;
- core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
- }
- }
}
static void resumeUpdate(WebViewCore core) {
@@ -1702,14 +1780,6 @@ final class WebViewCore {
sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
.obtainMessage(WebCoreThread.RESUME_PRIORITY));
- if (core != null) {
- synchronized (core) {
- core.mDrawIsScheduled = false;
- core.mDrawIsPaused = false;
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "resumeUpdate");
- core.contentDraw();
- }
- }
}
static void startCacheTransaction() {
@@ -1748,9 +1818,7 @@ final class WebViewCore {
}
// only fire an event if this is our first request
synchronized (this) {
- if (mDrawIsPaused || mDrawIsScheduled) {
- return;
- }
+ if (mDrawIsScheduled) return;
mDrawIsScheduled = true;
mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
}
@@ -1845,6 +1913,33 @@ final class WebViewCore {
}
}
+ private static boolean mRepaintScheduled = false;
+
+ /*
+ * Called by the WebView thread
+ */
+ /* package */ void signalRepaintDone() {
+ mRepaintScheduled = false;
+ }
+
+ // called by JNI
+ private void sendImmediateRepaint() {
+ if (mWebView != null && !mRepaintScheduled) {
+ mRepaintScheduled = true;
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.IMMEDIATE_REPAINT_MSG_ID).sendToTarget();
+ }
+ }
+
+ // called by JNI
+ private void setRootLayer(int layer) {
+ if (mWebView != null) {
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.SET_ROOT_LAYER_MSG_ID,
+ layer, 0).sendToTarget();
+ }
+ }
+
/* package */ WebView getWebView() {
return mWebView;
}
@@ -1861,7 +1956,14 @@ final class WebViewCore {
if (mWebView == null) return;
- setupViewport(standardLoad || mRestoredScale > 0);
+ boolean updateRestoreState = standardLoad || mRestoredScale > 0;
+ setupViewport(updateRestoreState);
+ // if updateRestoreState is true, ViewManager.postReadyToDrawAll() will
+ // be called after the WebView restore the state. If updateRestoreState
+ // is false, start to draw now as it is ready.
+ if (!updateRestoreState) {
+ mWebView.mViewManager.postReadyToDrawAll();
+ }
// reset the scroll position, the restored offset and scales
mWebkitScrollX = mWebkitScrollY = mRestoredX = mRestoredY
@@ -1956,12 +2058,14 @@ final class WebViewCore {
mRestoreState.mScrollY = mRestoredY;
mRestoreState.mMobileSite = (0 == mViewportWidth);
if (mRestoredScale > 0) {
- mRestoreState.mViewScale = mRestoredScale / 100.0f;
if (mRestoredScreenWidthScale > 0) {
mRestoreState.mTextWrapScale =
mRestoredScreenWidthScale / 100.0f;
+ // 0 will trigger WebView to turn on zoom overview mode
+ mRestoreState.mViewScale = 0;
} else {
- mRestoreState.mTextWrapScale = mRestoreState.mViewScale;
+ mRestoreState.mViewScale = mRestoreState.mTextWrapScale =
+ mRestoredScale / 100.0f;
}
} else {
if (mViewportInitialScale > 0) {
@@ -1994,7 +2098,6 @@ final class WebViewCore {
data.mTextWrapWidth = data.mWidth;
data.mScale = -1.0f;
data.mIgnoreHeight = false;
- data.mAnchorX = data.mAnchorY = 0;
// send VIEW_SIZE_CHANGED to the front of the queue so that we can
// avoid pushing the wrong picture to the WebView side. If there is
// a VIEW_SIZE_CHANGED in the queue, probably from WebView side,
@@ -2015,15 +2118,21 @@ final class WebViewCore {
// know the exact scale. If mRestoredScale is non-zero, use it;
// otherwise just use mTextWrapScale as the initial scale.
data.mScale = mRestoreState.mViewScale == 0
- ? (mRestoredScale > 0 ? mRestoredScale
+ ? (mRestoredScale > 0 ? mRestoredScale / 100.0f
: mRestoreState.mTextWrapScale)
: mRestoreState.mViewScale;
+ if (DebugFlags.WEB_VIEW_CORE) {
+ Log.v(LOGTAG, "setupViewport"
+ + " mRestoredScale=" + mRestoredScale
+ + " mViewScale=" + mRestoreState.mViewScale
+ + " mTextWrapScale=" + mRestoreState.mTextWrapScale
+ );
+ }
data.mWidth = Math.round(webViewWidth / data.mScale);
data.mHeight = mCurrentViewHeight * data.mWidth / viewportWidth;
data.mTextWrapWidth = Math.round(webViewWidth
/ mRestoreState.mTextWrapScale);
data.mIgnoreHeight = false;
- data.mAnchorX = data.mAnchorY = 0;
// send VIEW_SIZE_CHANGED to the front of the queue so that we
// can avoid pushing the wrong picture to the WebView side.
mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED);
@@ -2089,8 +2198,15 @@ final class WebViewCore {
WebView.CLEAR_TEXT_ENTRY).sendToTarget();
}
- private native void nativeUpdateFrameCacheIfLoading();
+ // called by JNI
+ private void sendFindAgain() {
+ if (mWebView == null) return;
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.FIND_AGAIN).sendToTarget();
+ }
+ private native void nativeUpdateFrameCacheIfLoading();
+ private native String nativeRequestLabel(int framePtr, int nodePtr);
/**
* Scroll the focused textfield to (xPercent, y) in document space
*/
@@ -2102,7 +2218,7 @@ final class WebViewCore {
private native void nativeSetGlobalBounds(int x, int y, int w, int h);
// called by JNI
- private void requestListBox(String[] array, boolean[] enabledArray,
+ private void requestListBox(String[] array, int[] enabledArray,
int[] selectedArray) {
if (mWebView != null) {
mWebView.requestListBox(array, enabledArray, selectedArray);
@@ -2110,7 +2226,7 @@ final class WebViewCore {
}
// called by JNI
- private void requestListBox(String[] array, boolean[] enabledArray,
+ private void requestListBox(String[] array, int[] enabledArray,
int selection) {
if (mWebView != null) {
mWebView.requestListBox(array, enabledArray, selection);
@@ -2119,102 +2235,128 @@ final class WebViewCore {
}
// called by JNI
- private void requestKeyboard(boolean showKeyboard) {
+ private void requestKeyboard(boolean showKeyboard, boolean isTextView) {
if (mWebView != null) {
Message.obtain(mWebView.mPrivateHandler,
- WebView.REQUEST_KEYBOARD, showKeyboard ? 1 : 0, 0)
+ WebView.REQUEST_KEYBOARD, showKeyboard ? 1 : 0,
+ isTextView ? 1 : 0)
.sendToTarget();
}
}
- // called by JNI. PluginWidget function to launch an activity and overlays
- // the activity with the View provided by the plugin class.
- private void startFullScreenPluginActivity(String libName, String clsName, int npp) {
+ // called by JNI
+ private Context getContext() {
+ return mContext;
+ }
+
+ // called by JNI
+ private Class<?> getPluginClass(String libName, String clsName) {
+
if (mWebView == null) {
- return;
+ return null;
}
+
+ PluginManager pluginManager = PluginManager.getInstance(null);
- String pkgName = PluginManager.getInstance(null).getPluginsAPKName(libName);
+ String pkgName = pluginManager.getPluginsAPKName(libName);
if (pkgName == null) {
Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK");
+ return null;
+ }
+
+ try {
+ return pluginManager.getPluginClass(pkgName, clsName);
+ } catch (NameNotFoundException e) {
+ Log.e(LOGTAG, "Unable to find plugin classloader for the apk (" + pkgName + ")");
+ } catch (ClassNotFoundException e) {
+ Log.e(LOGTAG, "Unable to find plugin class (" + clsName +
+ ") in the apk (" + pkgName + ")");
+ }
+
+ return null;
+ }
+
+ // called by JNI. PluginWidget function to launch a full-screen view using a
+ // View object provided by the plugin class.
+ private void showFullScreenPlugin(ViewManager.ChildView childView,
+ final int npp, int x, int y, int width, int height) {
+
+ if (mWebView == null) {
return;
}
- Intent intent = new Intent("android.intent.webkit.PLUGIN");
- intent.putExtra(PluginActivity.INTENT_EXTRA_PACKAGE_NAME, pkgName);
- intent.putExtra(PluginActivity.INTENT_EXTRA_CLASS_NAME, clsName);
- intent.putExtra(PluginActivity.INTENT_EXTRA_NPP_INSTANCE, npp);
- mWebView.getContext().startActivity(intent);
+ PluginFullScreenData data = new PluginFullScreenData();
+ data.mView = childView.mView;
+ data.mNpp = npp;
+ data.mDocX = x;
+ data.mDocY = y;
+ data.mDocWidth = width;
+ data.mDocHeight = height;
+ mWebView.mPrivateHandler.obtainMessage(WebView.SHOW_FULLSCREEN, data)
+ .sendToTarget();
}
- // called by JNI. PluginWidget functions for creating an embedded View for
- // the surface drawing model.
- private ViewManager.ChildView createSurface(String libName, String clsName,
- int npp, int x, int y, int width, int height) {
+ // called by JNI
+ private void hideFullScreenPlugin() {
if (mWebView == null) {
- return null;
+ return;
}
+ mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN)
+ .sendToTarget();
+ }
- String pkgName = PluginManager.getInstance(null).getPluginsAPKName(libName);
- if (pkgName == null) {
- Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK");
+ // called by JNI
+ private void updateFullScreenPlugin(int x, int y, int width, int height) {
+ if (mWebView == null) {
+ return;
+ }
+
+ PluginFullScreenData data = new PluginFullScreenData();
+ data.mDocX = x;
+ data.mDocY = y;
+ data.mDocWidth = width;
+ data.mDocHeight = height;
+ // null mView and mNpp to indicate it is an update
+ mWebView.mPrivateHandler.obtainMessage(WebView.SHOW_FULLSCREEN, data)
+ .sendToTarget();
+ }
+
+ // called by JNI. PluginWidget functions for creating an embedded View for
+ // the surface drawing model.
+ private ViewManager.ChildView addSurface(View pluginView, int x, int y,
+ int width, int height) {
+ if (mWebView == null) {
return null;
}
- PluginStub stub =PluginUtil.getPluginStub(mWebView.getContext(),pkgName, clsName);
- if (stub == null) {
- Log.e(LOGTAG, "Unable to find plugin class (" + clsName +
- ") in the apk (" + pkgName + ")");
+ if (pluginView == null) {
+ Log.e(LOGTAG, "Attempted to add an empty plugin view to the view hierarchy");
return null;
}
- View pluginView = stub.getEmbeddedView(npp, mWebView.getContext());
+ // ensures the view system knows the view can redraw itself
+ pluginView.setWillNotDraw(false);
ViewManager.ChildView view = mWebView.mViewManager.createView();
view.mView = pluginView;
view.attachView(x, y, width, height);
return view;
}
-
- private void destroySurface(ViewManager.ChildView childView) {
- childView.removeView();
+
+ private void updateSurface(ViewManager.ChildView childView, int x, int y,
+ int width, int height) {
+ childView.attachView(x, y, width, height);
}
- // called by JNI
- static class ShowRectData {
- int mLeft;
- int mTop;
- int mWidth;
- int mHeight;
- int mContentWidth;
- int mContentHeight;
- float mXPercentInDoc;
- float mXPercentInView;
- float mYPercentInDoc;
- float mYPercentInView;
- }
-
- private void showRect(int left, int top, int width, int height,
- int contentWidth, int contentHeight, float xPercentInDoc,
- float xPercentInView, float yPercentInDoc, float yPercentInView) {
- if (mWebView != null) {
- ShowRectData data = new ShowRectData();
- data.mLeft = left;
- data.mTop = top;
- data.mWidth = width;
- data.mHeight = height;
- data.mContentWidth = contentWidth;
- data.mContentHeight = contentHeight;
- data.mXPercentInDoc = xPercentInDoc;
- data.mXPercentInView = xPercentInView;
- data.mYPercentInDoc = yPercentInDoc;
- data.mYPercentInView = yPercentInView;
- Message.obtain(mWebView.mPrivateHandler, WebView.SHOW_RECT_MSG_ID,
- data).sendToTarget();
- }
+ private void destroySurface(ViewManager.ChildView childView) {
+ childView.removeView();
}
private native void nativePause();
private native void nativeResume();
private native void nativeFreeMemory();
+ private native void nativeFullScreenPluginHidden(int npp);
+ private native boolean nativeValidNodeAndBounds(int frame, int node,
+ Rect bounds);
+
}
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 6e10811..110e4f8 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -27,6 +27,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import android.webkit.CookieManager.Cookie;
@@ -174,7 +175,16 @@ public class WebViewDatabase {
public static synchronized WebViewDatabase getInstance(Context context) {
if (mInstance == null) {
mInstance = new WebViewDatabase();
- mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
+ try {
+ mDatabase = context
+ .openOrCreateDatabase(DATABASE_FILE, 0, null);
+ } catch (SQLiteException e) {
+ // try again by deleting the old db and create a new one
+ if (context.deleteDatabase(DATABASE_FILE)) {
+ mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
+ null);
+ }
+ }
// mDatabase should not be null,
// the only case is RequestAPI test has problem to create db
@@ -194,8 +204,16 @@ public class WebViewDatabase {
mDatabase.setLockingEnabled(false);
}
- mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE,
- 0, null);
+ try {
+ mCacheDatabase = context.openOrCreateDatabase(
+ CACHE_DATABASE_FILE, 0, null);
+ } catch (SQLiteException e) {
+ // try again by deleting the old db and create a new one
+ if (context.deleteDatabase(CACHE_DATABASE_FILE)) {
+ mCacheDatabase = context.openOrCreateDatabase(
+ CACHE_DATABASE_FILE, 0, null);
+ }
+ }
// mCacheDatabase should not be null,
// the only case is RequestAPI test has problem to create db
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 271989a..e241c77 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -127,11 +127,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
static final int TOUCH_MODE_FLING = 4;
/**
- * Indicates that the user is currently dragging the fast scroll thumb
- */
- static final int TOUCH_MODE_FAST_SCROLL = 5;
-
- /**
* Regular layout - usually an unsolicited layout from the view system
*/
static final int LAYOUT_NORMAL = 0;
@@ -440,6 +435,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private Runnable mClearScrollingCache;
private int mMinimumVelocity;
private int mMaximumVelocity;
+
+ final boolean[] mIsScrap = new boolean[1];
/**
* Interface definition for a callback to be invoked when the list or grid
@@ -1239,9 +1236,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* converting an old view or making a new one.
*
* @param position The position to display
+ * @param isScrap Array of at least 1 boolean, the first entry will become true if
+ * the returned view was taken from the scrap heap, false if otherwise.
+ *
* @return A view displaying the data associated with the specified position
*/
- View obtainView(int position) {
+ View obtainView(int position, boolean[] isScrap) {
+ isScrap[0] = false;
View scrapView;
scrapView = mRecycler.getScrapView(position);
@@ -1269,6 +1270,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
position, -1);
}
+ } else {
+ isScrap[0] = true;
}
} else {
child = mAdapter.getView(position, null, this);
@@ -1543,6 +1546,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Dismiss the popup in case onSaveInstanceState() was not invoked
dismissPopup();
+ // Detach any view left in the scrap heap
+ mRecycler.clear();
+
final ViewTreeObserver treeObserver = getViewTreeObserver();
if (treeObserver != null) {
treeObserver.removeOnTouchModeChangeListener(this);
@@ -1636,6 +1642,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mDataChanged) return;
if (mAdapter != null && mItemCount > 0 &&
+ mClickMotionPosition != INVALID_POSITION &&
mClickMotionPosition < mAdapter.getCount() && sameWindow()) {
performItemClick(mChild, mClickMotionPosition, getAdapter().getItemId(
mClickMotionPosition));
@@ -2109,6 +2116,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
mFlingRunnable.start(-initialVelocity);
+ } else {
+ mTouchMode = TOUCH_MODE_REST;
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
}
} else {
@@ -2966,7 +2976,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
break;
case KeyEvent.KEYCODE_SPACE:
// Only send spaces once we are filtered
- okToSend = mFiltered = true;
+ okToSend = mFiltered;
break;
}
@@ -3563,6 +3573,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// into the scrap heap
int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
+ removeDetachedView(scrap, false);
return;
}
@@ -3590,12 +3601,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
for (int i = 0; i < count; ++i) {
final View victim = activeViews[i];
if (victim != null) {
- int whichScrap = ((AbsListView.LayoutParams)
- victim.getLayoutParams()).viewType;
+ int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType;
activeViews[i] = null;
if (whichScrap == AdapterView.ITEM_VIEW_TYPE_IGNORE) {
+ removeDetachedView(victim, false);
// Do not move views that should be ignored
continue;
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index d25530b..d6dd872 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -26,7 +26,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
public abstract class AbsSeekBar extends ProgressBar {
-
private Drawable mThumb;
private int mThumbOffset;
@@ -66,8 +65,9 @@ public abstract class AbsSeekBar extends ProgressBar {
Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
setThumb(thumb); // will guess mThumbOffset if thumb != null...
// ...but allow layout to override this
- int thumbOffset =
- a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
+ int thumbOffset = a.getDimensionPixelOffset(
+ com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
+ setThumbOffset(thumbOffset);
a.recycle();
a = context.obtainStyledAttributes(attrs,
@@ -91,7 +91,7 @@ public abstract class AbsSeekBar extends ProgressBar {
// Assuming the thumb drawable is symmetric, set the thumb offset
// such that the thumb will hang halfway off either edge of the
// progress bar.
- mThumbOffset = (int)thumb.getIntrinsicWidth() / 2;
+ mThumbOffset = thumb.getIntrinsicWidth() / 2;
}
mThumb = thumb;
invalidate();
@@ -368,20 +368,21 @@ public abstract class AbsSeekBar extends ProgressBar {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- int progress = getProgress();
-
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (progress <= 0) break;
- setProgress(progress - mKeyProgressIncrement, true);
- onKeyChange();
- return true;
-
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (progress >= getMax()) break;
- setProgress(progress + mKeyProgressIncrement, true);
- onKeyChange();
- return true;
+ if (isEnabled()) {
+ int progress = getProgress();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (progress <= 0) break;
+ setProgress(progress - mKeyProgressIncrement, true);
+ onKeyChange();
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (progress >= getMax()) break;
+ setProgress(progress + mKeyProgressIncrement, true);
+ onKeyChange();
+ return true;
+ }
}
return super.onKeyDown(keyCode, event);
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 424a936..c939e3f 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -249,7 +249,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
diff --git a/core/java/android/widget/AbsoluteLayout.java b/core/java/android/widget/AbsoluteLayout.java
index c77f7ae..b829655 100644
--- a/core/java/android/widget/AbsoluteLayout.java
+++ b/core/java/android/widget/AbsoluteLayout.java
@@ -161,9 +161,9 @@ public class AbsoluteLayout extends ViewGroup {
* Creates a new set of layout parameters with the specified width,
* height and location.
*
- * @param width the width, either {@link #FILL_PARENT},
+ * @param width the width, either {@link #MATCH_PARENT},
{@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #FILL_PARENT},
+ * @param height the height, either {@link #MATCH_PARENT},
{@link #WRAP_CONTENT} or a fixed size in pixels
* @param x the X location of the child
* @param y the Y location of the child
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 75d0f31..bb9a672 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -172,7 +172,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
View.NO_ID);
- // For dropdown width, the developer can specify a specific width, or FILL_PARENT
+ // For dropdown width, the developer can specify a specific width, or MATCH_PARENT
// (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -240,7 +240,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Returns the current width for the auto-complete drop down list. This can
- * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
* {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
*
* @return the width for the drop down list
@@ -253,7 +253,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Sets the current width for the auto-complete drop down list. This can
- * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
* {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
*
* @param width the width to use
@@ -266,7 +266,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Returns the current height for the auto-complete drop down list. This can
- * be a fixed height, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill
+ * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill
* the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
* of the drop down's content.</p>
*
@@ -280,7 +280,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Sets the current height for the auto-complete drop down list. This can
- * be a fixed height, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill
+ * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill
* the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
* of the drop down's content.</p>
*
@@ -1027,7 +1027,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
- performValidation();
if (!hasWindowFocus && !mDropDownAlwaysVisible) {
dismissDropDown();
}
@@ -1036,7 +1035,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
- performValidation();
+ // Perform validation if the view is losing focus.
+ if (!focused) {
+ performValidation();
+ }
if (!focused && !mDropDownAlwaysVisible) {
dismissDropDown();
}
@@ -1127,7 +1129,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
boolean noInputMethod = isInputMethodNotNeeded();
if (mPopup.isShowing()) {
- if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+ if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
widthSpec = -1;
@@ -1137,19 +1139,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
widthSpec = mDropDownWidth;
}
- if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
+ if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
- heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.FILL_PARENT;
+ heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
if (noInputMethod) {
mPopup.setWindowLayoutMode(
- mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
- ViewGroup.LayoutParams.FILL_PARENT : 0, 0);
+ mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+ ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
} else {
mPopup.setWindowLayoutMode(
- mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
- ViewGroup.LayoutParams.FILL_PARENT : 0,
- ViewGroup.LayoutParams.FILL_PARENT);
+ mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+ ViewGroup.LayoutParams.MATCH_PARENT : 0,
+ ViewGroup.LayoutParams.MATCH_PARENT);
}
} else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
heightSpec = height;
@@ -1162,8 +1164,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, widthSpec, heightSpec);
} else {
- if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
- widthSpec = ViewGroup.LayoutParams.FILL_PARENT;
+ if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
+ widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
mPopup.setWidth(getDropDownAnchorView().getWidth());
@@ -1172,8 +1174,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
}
- if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
- heightSpec = ViewGroup.LayoutParams.FILL_PARENT;
+ if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+ heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
mPopup.setHeight(height);
@@ -1293,7 +1295,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
hintContainer.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT, 0, 1.0f
+ ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
);
hintContainer.addView(dropDownView, hintParams);
hintContainer.addView(hintView);
@@ -1329,7 +1331,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
final int maxHeight = mPopup.getMaxAvailableHeight(
getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
- if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
+ if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
// getMaxAvailableHeight() subtracts the padding, so we put it back,
// to get the available height for the whole window
int padding = 0;
@@ -1483,8 +1485,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* @return the view for the specified item
*/
@Override
- protected View obtainView(int position) {
- View view = super.obtainView(position);
+ View obtainView(int position, boolean[] isScrap) {
+ View view = super.obtainView(position, isScrap);
if (view instanceof TextView) {
((TextView) view).setHorizontallyScrolling(true);
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index fd590ed..aa9062b 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -117,11 +117,11 @@ public class CheckedTextView extends TextView implements Checkable {
* @param d The Drawable to use for the checkmark.
*/
public void setCheckMarkDrawable(Drawable d) {
+ if (mCheckMarkDrawable != null) {
+ mCheckMarkDrawable.setCallback(null);
+ unscheduleDrawable(mCheckMarkDrawable);
+ }
if (d != null) {
- if (mCheckMarkDrawable != null) {
- mCheckMarkDrawable.setCallback(null);
- unscheduleDrawable(mCheckMarkDrawable);
- }
d.setCallback(this);
d.setVisible(getVisibility() == VISIBLE, false);
d.setState(CHECKED_STATE_SET);
@@ -130,10 +130,10 @@ public class CheckedTextView extends TextView implements Checkable {
mCheckMarkWidth = d.getIntrinsicWidth();
mPaddingRight = mCheckMarkWidth + mBasePaddingRight;
d.setState(getDrawableState());
- mCheckMarkDrawable = d;
} else {
mPaddingRight = mBasePaddingRight;
}
+ mCheckMarkDrawable = d;
requestLayout();
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 5e76cc3..b657e8e 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -26,9 +26,9 @@ import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
+import com.android.common.widget.NumberPicker;
+import com.android.common.widget.NumberPicker.OnChangedListener;
import com.android.internal.R;
-import com.android.internal.widget.NumberPicker;
-import com.android.internal.widget.NumberPicker.OnChangedListener;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 6abb2ae..405461a 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -33,7 +33,6 @@ import android.view.ContextMenu;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
-import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ExpandableListConnector.PositionMetadata;
/**
@@ -514,37 +513,36 @@ public class ExpandableListView extends ListView {
boolean returnValue;
if (posMetadata.position.type == ExpandableListPosition.GROUP) {
/* It's a group, so handle collapsing/expanding */
-
+
+ /* It's a group click, so pass on event */
+ if (mOnGroupClickListener != null) {
+ if (mOnGroupClickListener.onGroupClick(this, v,
+ posMetadata.position.groupPos, id)) {
+ posMetadata.recycle();
+ return true;
+ }
+ }
+
if (posMetadata.isExpanded()) {
/* Collapse it */
mConnector.collapseGroup(posMetadata);
playSoundEffect(SoundEffectConstants.CLICK);
-
+
if (mOnGroupCollapseListener != null) {
mOnGroupCollapseListener.onGroupCollapse(posMetadata.position.groupPos);
}
-
} else {
- /* It's a group click, so pass on event */
- if (mOnGroupClickListener != null) {
- if (mOnGroupClickListener.onGroupClick(this, v,
- posMetadata.position.groupPos, id)) {
- posMetadata.recycle();
- return true;
- }
- }
-
/* Expand it */
mConnector.expandGroup(posMetadata);
playSoundEffect(SoundEffectConstants.CLICK);
-
+
if (mOnGroupExpandListener != null) {
mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
}
}
-
+
returnValue = true;
} else {
/* It's a child, so pass on event */
@@ -553,12 +551,12 @@ public class ExpandableListView extends ListView {
return mOnChildClickListener.onChildClick(this, v, posMetadata.position.groupPos,
posMetadata.position.childPos, id);
}
-
+
returnValue = false;
}
-
+
posMetadata.recycle();
-
+
return returnValue;
}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 3afd5d4..65a4673 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -164,12 +164,12 @@ public class FrameLayout extends ViewGroup {
/**
* Returns a set of layout parameters with a width of
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
- * and a height of {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}.
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
+ * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
*/
@Override
protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
/**
@@ -467,9 +467,9 @@ public class FrameLayout extends ViewGroup {
* Creates a new set of layout parameters with the specified width, height
* and weight.
*
- * @param width the width, either {@link #FILL_PARENT},
+ * @param width the width, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #FILL_PARENT},
+ * @param height the height, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param gravity the gravity
*
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index ffe9908..30a38df 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -931,11 +931,11 @@ public class GridView extends AbsListView {
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
final int count = mItemCount;
if (count > 0) {
- final View child = obtainView(0);
+ final View child = obtainView(0, mIsScrap);
AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
if (p == null) {
- p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
child.setLayoutParams(p);
}
@@ -1203,7 +1203,7 @@ public class GridView extends AbsListView {
View child;
if (!mDataChanged) {
- // Try to use an exsiting view for this position
+ // Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
@@ -1215,10 +1215,10 @@ public class GridView extends AbsListView {
// Make a new view for this position, or convert an unused view if
// possible
- child = obtainView(position);
+ child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
- setupChild(child, position, y, flow, childrenLeft, selected, false, where);
+ setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
return child;
}
@@ -1254,7 +1254,7 @@ public class GridView extends AbsListView {
// some up...
AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
if (p == null) {
- p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
p.viewType = mAdapter.getItemViewType(position);
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index b8f0a7e..3853359 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -489,7 +490,18 @@ public class ImageView extends View {
mUri = null;
}
} else if (mUri != null) {
- if ("content".equals(mUri.getScheme())) {
+ String scheme = mUri.getScheme();
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ try {
+ // Load drawable through Resources, to get the source density information
+ ContentResolver.OpenResourceIdResult r =
+ mContext.getContentResolver().getResourceId(mUri);
+ d = r.r.getDrawable(r.id);
+ } catch (Exception e) {
+ Log.w("ImageView", "Unable to open content: " + mUri, e);
+ }
+ } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme)) {
try {
d = Drawable.createFromStream(
mContext.getContentResolver().openInputStream(mUri),
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 6cc794b..37372c5 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -378,7 +378,7 @@ public class LinearLayout extends ViewGroup {
}
boolean matchWidthLocally = false;
- if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.FILL_PARENT) {
+ if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
@@ -391,7 +391,7 @@ public class LinearLayout extends ViewGroup {
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
- allFillParent = allFillParent && lp.width == LayoutParams.FILL_PARENT;
+ allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
@@ -472,12 +472,12 @@ public class LinearLayout extends ViewGroup {
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
- lp.width == LayoutParams.FILL_PARENT;
+ lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
- allFillParent = allFillParent && lp.width == LayoutParams.FILL_PARENT;
+ allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
mTotalLength += child.getMeasuredHeight() + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child);
@@ -515,7 +515,7 @@ public class LinearLayout extends ViewGroup {
if (child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
- if (lp.width == LayoutParams.FILL_PARENT) {
+ if (lp.width == LayoutParams.MATCH_PARENT) {
// Temporarily force children to reuse their old measured height
// FIXME: this may not be right for something like wrapping text?
int oldHeight = lp.height;
@@ -629,7 +629,7 @@ public class LinearLayout extends ViewGroup {
}
boolean matchHeightLocally = false;
- if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.FILL_PARENT) {
+ if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) {
// The height of the linear layout will scale, and at least one
// child said it wanted to match our height. Set a flag indicating that
// we need to remeasure at least that view when we know our height.
@@ -657,7 +657,7 @@ public class LinearLayout extends ViewGroup {
maxHeight = Math.max(maxHeight, childHeight);
- allFillParent = allFillParent && lp.height == LayoutParams.FILL_PARENT;
+ allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Heights of weighted Views are bogus if we end up
@@ -758,7 +758,7 @@ public class LinearLayout extends ViewGroup {
lp.rightMargin + getNextLocationOffset(child);
boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
- lp.height == LayoutParams.FILL_PARENT;
+ lp.height == LayoutParams.MATCH_PARENT;
final int margin = lp.topMargin + lp .bottomMargin;
int childHeight = child.getMeasuredHeight() + margin;
@@ -766,7 +766,7 @@ public class LinearLayout extends ViewGroup {
alternativeMaxHeight = Math.max(alternativeMaxHeight,
matchHeightLocally ? margin : childHeight);
- allFillParent = allFillParent && lp.height == LayoutParams.FILL_PARENT;
+ allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
if (baselineAligned) {
final int childBaseline = child.getBaseline();
@@ -832,7 +832,7 @@ public class LinearLayout extends ViewGroup {
if (child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
- if (lp.height == LayoutParams.FILL_PARENT) {
+ if (lp.height == LayoutParams.MATCH_PARENT) {
// Temporarily force children to reuse their old measured width
// FIXME: this may not be right for something like wrapping text?
int oldWidth = lp.width;
@@ -991,6 +991,9 @@ public class LinearLayout extends ViewGroup {
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
+ default:
+ childLeft = paddingLeft;
+ break;
}
@@ -1062,7 +1065,7 @@ public class LinearLayout extends ViewGroup {
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
- if (baselineAligned && lp.height != LayoutParams.FILL_PARENT) {
+ if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
childBaseline = child.getBaseline();
}
@@ -1102,6 +1105,9 @@ public class LinearLayout extends ViewGroup {
childTop -= (maxDescent[INDEX_BOTTOM] - descent);
}
break;
+ default:
+ childTop = paddingTop;
+ break;
}
childLeft += lp.leftMargin;
@@ -1193,7 +1199,7 @@ public class LinearLayout extends ViewGroup {
/**
* Returns a set of layout parameters with a width of
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
* and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
* when the layout's orientation is {@link #VERTICAL}. When the orientation is
* {@link #HORIZONTAL}, the width is set to {@link LayoutParams#WRAP_CONTENT}
@@ -1204,7 +1210,7 @@ public class LinearLayout extends ViewGroup {
if (mOrientation == HORIZONTAL) {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} else if (mOrientation == VERTICAL) {
- return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
return null;
}
@@ -1284,9 +1290,9 @@ public class LinearLayout extends ViewGroup {
* Creates a new set of layout parameters with the specified width, height
* and weight.
*
- * @param width the width, either {@link #FILL_PARENT},
+ * @param width the width, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #FILL_PARENT},
+ * @param height the height, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param weight the weight
*/
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 7c8151e..c63774a 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1033,7 +1033,7 @@ public class ListView extends AbsListView {
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
heightMode == MeasureSpec.UNSPECIFIED)) {
- final View child = obtainView(0);
+ final View child = obtainView(0, mIsScrap);
measureScrapChild(child, 0, widthMeasureSpec);
@@ -1067,7 +1067,7 @@ public class ListView extends AbsListView {
private void measureScrapChild(View child, int position, int widthMeasureSpec) {
LayoutParams p = (LayoutParams) child.getLayoutParams();
if (p == null) {
- p = new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ p = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
child.setLayoutParams(p);
}
@@ -1142,9 +1142,10 @@ public class ListView extends AbsListView {
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
final boolean recyle = recycleOnMeasure();
+ final boolean[] isScrap = mIsScrap;
for (i = startPosition; i <= endPosition; ++i) {
- child = obtainView(i);
+ child = obtainView(i, isScrap);
measureScrapChild(child, i, widthMeasureSpec);
@@ -1374,7 +1375,7 @@ public class ListView extends AbsListView {
int childrenBottom = mBottom - mTop - mListPadding.bottom;
int childCount = getChildCount();
- int index;
+ int index = 0;
int delta = 0;
View sel;
@@ -1665,10 +1666,10 @@ public class ListView extends AbsListView {
}
// Make a new view for this position, or convert an unused view if possible
- child = obtainView(position);
+ child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
- setupChild(child, position, y, flow, childrenLeft, selected, false);
+ setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
@@ -1701,7 +1702,7 @@ public class ListView extends AbsListView {
// noinspection unchecked
AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
if (p == null) {
- p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
p.viewType = mAdapter.getItemViewType(position);
@@ -2387,7 +2388,7 @@ public class ListView extends AbsListView {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
@@ -2823,17 +2824,19 @@ public class ListView extends AbsListView {
private View addViewAbove(View theView, int position) {
int abovePosition = position - 1;
- View view = obtainView(abovePosition);
+ View view = obtainView(abovePosition, mIsScrap);
int edgeOfNewChild = theView.getTop() - mDividerHeight;
- setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left, false, false);
+ setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
+ false, mIsScrap[0]);
return view;
}
private View addViewBelow(View theView, int position) {
int belowPosition = position + 1;
- View view = obtainView(belowPosition);
+ View view = obtainView(belowPosition, mIsScrap);
int edgeOfNewChild = theView.getBottom() + mDividerHeight;
- setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left, false, false);
+ setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
+ false, mIsScrap[0]);
return view;
}
@@ -3080,13 +3083,19 @@ public class ListView extends AbsListView {
if (gainFocus && previouslyFocusedRect != null) {
previouslyFocusedRect.offset(mScrollX, mScrollY);
+ final ListAdapter adapter = mAdapter;
+ final int firstPosition = mFirstPosition;
+ // Don't cache the result of getChildCount here, it could change in layoutChildren.
+ if (adapter.getCount() < getChildCount() + firstPosition) {
+ mLayoutMode = LAYOUT_NORMAL;
+ layoutChildren();
+ }
+
// figure out which item should be selected based on previously
// focused rect
Rect otherRect = mTempRect;
int minDistance = Integer.MAX_VALUE;
final int childCount = getChildCount();
- final int firstPosition = mFirstPosition;
- final ListAdapter adapter = mAdapter;
for (int i = 0; i < childCount; i++) {
// only consider selectable views
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index 446a992..c246c247 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -167,8 +167,8 @@ public class MediaController extends FrameLayout {
mAnchor = view;
FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
);
removeAllViews();
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index e4cc609..d20ab3b 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -569,7 +569,7 @@ public class PopupWindow {
* the current width or height is requested as an explicit size from
* the window manager. You can supply
* {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
- * {@link ViewGroup.LayoutParams#FILL_PARENT} to have that measure
+ * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
* spec supplied instead, replacing the absolute width and height that
* has been set in the popup.</p>
*
@@ -578,11 +578,11 @@ public class PopupWindow {
*
* @param widthSpec an explicit width measure spec mode, either
* {@link ViewGroup.LayoutParams#WRAP_CONTENT},
- * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
+ * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
* width.
* @param heightSpec an explicit height measure spec mode, either
* {@link ViewGroup.LayoutParams#WRAP_CONTENT},
- * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
+ * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
* height.
*/
public void setWindowLayoutMode(int widthSpec, int heightSpec) {
@@ -785,7 +785,7 @@ public class PopupWindow {
if (mBackground != null) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
- int height = ViewGroup.LayoutParams.FILL_PARENT;
+ int height = ViewGroup.LayoutParams.MATCH_PARENT;
if (layoutParams != null &&
layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -795,7 +795,7 @@ public class PopupWindow {
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT, height
+ ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.setBackgroundDrawable(mBackground);
popupViewContainer.addView(mContentView, listParams);
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 8019f14..07c3e4b 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -25,9 +25,9 @@ import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.util.AttributeSet;
@@ -55,21 +55,28 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
static final private int TOKEN_PHONE_LOOKUP = 1;
static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
+ static final private int TOKEN_CONTACT_LOOKUP_AND_TRIGGER = 4;
static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
RawContacts.CONTACT_ID,
Contacts.LOOKUP_KEY,
};
- static int EMAIL_ID_COLUMN_INDEX = 0;
- static int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final int EMAIL_ID_COLUMN_INDEX = 0;
+ static final int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
PhoneLookup._ID,
PhoneLookup.LOOKUP_KEY,
};
- static int PHONE_ID_COLUMN_INDEX = 0;
- static int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final int PHONE_ID_COLUMN_INDEX = 0;
+ static final int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
+ Contacts._ID,
+ Contacts.LOOKUP_KEY,
+ };
+ static final int CONTACT_ID_COLUMN_INDEX = 0;
+ static final int CONTACT_LOOKUPKEY_COLUMN_INDEX = 1;
public QuickContactBadge(Context context) {
@@ -181,9 +188,9 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
public void onClick(View v) {
if (mContactUri != null) {
- final ContentResolver resolver = getContext().getContentResolver();
- final Uri lookupUri = Contacts.getLookupUri(resolver, mContactUri);
- trigger(lookupUri);
+ mQueryHandler.startQuery(TOKEN_CONTACT_LOOKUP_AND_TRIGGER, null,
+ mContactUri,
+ CONTACT_LOOKUP_PROJECTION, null, null, null);
} else if (mContactEmail != null) {
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
@@ -249,6 +256,17 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
lookupUri = Contacts.getLookupUri(contactId, lookupKey);
}
}
+
+ case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: {
+ if (cursor != null && cursor.moveToFirst()) {
+ long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
+ String lookupKey = cursor.getString(CONTACT_LOOKUPKEY_COLUMN_INDEX);
+ lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ trigger = true;
+ }
+
+ break;
+ }
}
} finally {
if (cursor != null) {
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index e19a93d..1aa1df3 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -45,8 +45,7 @@ import java.util.ArrayList;
/**
* A Layout where the positions of the children can be described in relation to each other or to the
- * parent. For the sake of efficiency, the relations between views are evaluated in one pass, so if
- * view Y is dependent on the position of view X, make sure the view X comes first in the layout.
+ * parent.
*
* <p>
* Note that you cannot have a circular dependency between the size of the RelativeLayout and the
@@ -292,6 +291,8 @@ public class RelativeLayout extends ViewGroup {
}
}
+ // TODO: we need to find another way to implement RelativeLayout
+ // This implementation cannot handle every case
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDirtyHierarchy) {
@@ -439,6 +440,10 @@ public class RelativeLayout extends ViewGroup {
final int[] rules = params.getRules();
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
centerHorizontal(child, params, width);
+ } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
+ final int childWidth = child.getMeasuredWidth();
+ params.mLeft = width - mPaddingRight - childWidth;
+ params.mRight = params.mLeft + childWidth;
}
}
}
@@ -465,6 +470,10 @@ public class RelativeLayout extends ViewGroup {
final int[] rules = params.getRules();
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
centerVertical(child, params, height);
+ } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
+ final int childHeight = child.getMeasuredHeight();
+ params.mTop = height - mPaddingBottom - childHeight;
+ params.mBottom = params.mTop + childHeight;
}
}
}
@@ -561,7 +570,7 @@ public class RelativeLayout extends ViewGroup {
mPaddingLeft, mPaddingRight,
myWidth);
int childHeightMeasureSpec;
- if (params.width == LayoutParams.FILL_PARENT) {
+ if (params.width == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
@@ -623,7 +632,7 @@ public class RelativeLayout extends ViewGroup {
// We can grow in this dimension.
childSpecSize = childSize;
}
- } else if (childSize == LayoutParams.FILL_PARENT) {
+ } else if (childSize == LayoutParams.MATCH_PARENT) {
// Child wanted to be as big as possible. Give all availble
// space
childSpecMode = MeasureSpec.EXACTLY;
@@ -674,7 +683,7 @@ public class RelativeLayout extends ViewGroup {
params.mRight = params.mLeft + child.getMeasuredWidth();
}
}
- return false;
+ return rules[ALIGN_PARENT_RIGHT] != 0;
}
private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
@@ -703,7 +712,7 @@ public class RelativeLayout extends ViewGroup {
params.mBottom = params.mTop + child.getMeasuredHeight();
}
}
- return false;
+ return rules[ALIGN_PARENT_BOTTOM] != 0;
}
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b847e57..3003580 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -26,6 +26,7 @@ import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -37,7 +38,6 @@ import android.view.ViewGroup;
import android.view.LayoutInflater.Filter;
import android.view.View.OnClickListener;
-import java.lang.Class;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -137,11 +137,21 @@ public class RemoteViews implements Parcelable, Filter {
if (target != null && pendingIntent != null) {
OnClickListener listener = new OnClickListener() {
public void onClick(View v) {
- int[] pos = new int[2];
+ // Find target view location in screen coordinates and
+ // fill into PendingIntent before sending.
+ final float appScale = v.getContext().getResources()
+ .getCompatibilityInfo().applicationScale;
+ final int[] pos = new int[2];
v.getLocationOnScreen(pos);
- Intent intent = new Intent();
- intent.setSourceBounds(new Rect(pos[0], pos[1],
- pos[0]+v.getWidth(), pos[1]+v.getHeight()));
+
+ final Rect rect = new Rect();
+ rect.left = (int) (pos[0] * appScale + 0.5f);
+ rect.top = (int) (pos[1] * appScale + 0.5f);
+ rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
+ rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
+
+ final Intent intent = new Intent();
+ intent.setSourceBounds(rect);
try {
// TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
v.getContext().startIntentSender(
@@ -273,6 +283,7 @@ public class RemoteViews implements Parcelable, Filter {
static final int CHAR_SEQUENCE = 10;
static final int URI = 11;
static final int BITMAP = 12;
+ static final int BUNDLE = 13;
int viewId;
String methodName;
@@ -332,6 +343,9 @@ public class RemoteViews implements Parcelable, Filter {
case BITMAP:
this.value = Bitmap.CREATOR.createFromParcel(in);
break;
+ case BUNDLE:
+ this.value = in.readBundle();
+ break;
default:
break;
}
@@ -384,6 +398,9 @@ public class RemoteViews implements Parcelable, Filter {
case BITMAP:
((Bitmap)this.value).writeToParcel(out, flags);
break;
+ case BUNDLE:
+ out.writeBundle((Bundle) this.value);
+ break;
default:
break;
}
@@ -415,6 +432,8 @@ public class RemoteViews implements Parcelable, Filter {
return Uri.class;
case BITMAP:
return Bitmap.class;
+ case BUNDLE:
+ return Bundle.class;
default:
return null;
}
@@ -876,6 +895,17 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Call a method taking one Bundle on a view in the layout for this RemoteViews.
+ *
+ * @param viewId The id of the view whose text should change
+ * @param methodName The name of the method to call.
+ * @param value The value to pass to the method.
+ */
+ public void setBundle(int viewId, String methodName, Bundle value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
+ }
+
+ /**
* Inflates the view hierarchy represented by this object and applies
* all of the actions.
*
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 24d97a5..bf16e28 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -51,8 +51,6 @@ import java.util.List;
* <p>ScrollView only supports vertical scrolling.
*/
public class ScrollView extends FrameLayout {
- static final String TAG = "ScrollView";
-
static final int ANIMATED_SCROLL_GAP = 250;
static final float MAX_SCROLL_FACTOR = 0.5f;
@@ -401,6 +399,7 @@ public class ScrollView extends FrameLayout {
final int yDiff = (int) Math.abs(y - mLastMotionY);
if (yDiff > mTouchSlop) {
mIsBeingDragged = true;
+ mLastMotionY = y;
}
break;
diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java
index 9dd4d15..479965a 100644
--- a/core/java/android/widget/SimpleAdapter.java
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -25,7 +25,6 @@ import android.net.Uri;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.WeakHashMap;
/**
* An easy adapter to map static data to views defined in an XML file. You can specify the data
@@ -58,7 +57,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
private int mResource;
private int mDropDownResource;
private LayoutInflater mInflater;
- private final WeakHashMap<View, View[]> mHolders = new WeakHashMap<View, View[]>();
private SimpleFilter mFilter;
private ArrayList<Map<String, ?>> mUnfilteredData;
@@ -121,16 +119,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
View v;
if (convertView == null) {
v = mInflater.inflate(resource, parent, false);
-
- final int[] to = mTo;
- final int count = to.length;
- final View[] holder = new View[count];
-
- for (int i = 0; i < count; i++) {
- holder[i] = v.findViewById(to[i]);
- }
-
- mHolders.put(v, holder);
} else {
v = convertView;
}
@@ -162,13 +150,12 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
}
final ViewBinder binder = mViewBinder;
- final View[] holder = mHolders.get(view);
final String[] from = mFrom;
final int[] to = mTo;
final int count = to.length;
for (int i = 0; i < count; i++) {
- final View v = holder[i];
+ final View v = view.findViewById(to[i]);
if (v != null) {
final Object data = dataSet.get(from[i]);
String text = data == null ? "" : data.toString();
@@ -187,7 +174,8 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
((Checkable) v).setChecked((Boolean) data);
} else {
throw new IllegalStateException(v.getClass().getName() +
- " should be bound to a Boolean, not a " + data.getClass());
+ " should be bound to a Boolean, not a " +
+ (data == null ? "<unknown type>" : data.getClass()));
}
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
index 436b79b..7d3459e 100644
--- a/core/java/android/widget/SimpleCursorAdapter.java
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -20,9 +20,6 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.WeakHashMap;
/**
* An easy adapter to map columns from a cursor to TextViews or ImageViews
@@ -66,7 +63,6 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
private CursorToStringConverter mCursorToStringConverter;
private ViewBinder mViewBinder;
private String[] mOriginalFrom;
- private final WeakHashMap<View, View[]> mHolders = new WeakHashMap<View, View[]>();
/**
* Constructor.
@@ -91,29 +87,6 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
findColumns(from);
}
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return generateViewHolder(super.newView(context, cursor, parent));
- }
-
- @Override
- public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
- return generateViewHolder(super.newDropDownView(context, cursor, parent));
- }
-
- private View generateViewHolder(View v) {
- final int[] to = mTo;
- final int count = to.length;
- final View[] holder = new View[count];
-
- for (int i = 0; i < count; i++) {
- holder[i] = v.findViewById(to[i]);
- }
- mHolders.put(v, holder);
-
- return v;
- }
-
/**
* Binds all of the field names passed into the "to" parameter of the
* constructor with their corresponding cursor columns as specified in the
@@ -140,13 +113,13 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
- final View[] holder = mHolders.get(view);
final ViewBinder binder = mViewBinder;
final int count = mTo.length;
final int[] from = mFrom;
+ final int[] to = mTo;
for (int i = 0; i < count; i++) {
- final View v = holder[i];
+ final View v = view.findViewById(to[i]);
if (v != null) {
boolean bound = false;
if (binder != null) {
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index f706744..11d72de 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -43,7 +43,7 @@ import android.view.accessibility.AccessibilityEvent;
* SlidingDrawer should be used as an overlay inside layouts. This means SlidingDrawer
* should only be used inside of a FrameLayout or a RelativeLayout for instance. The
* size of the SlidingDrawer defines how much space the content will occupy once slid
- * out so SlidingDrawer should usually use fill_parent for both its dimensions.
+ * out so SlidingDrawer should usually use match_parent for both its dimensions.
*
* Inside an XML layout, SlidingDrawer must define the id of the handle and of the
* content:
@@ -51,8 +51,8 @@ import android.view.accessibility.AccessibilityEvent;
* <pre class="prettyprint">
* &lt;SlidingDrawer
* android:id="@+id/drawer"
- * android:layout_width="fill_parent"
- * android:layout_height="fill_parent"
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent"
*
* android:handle="@+id/handle"
* android:content="@+id/content"&gt;
@@ -64,8 +64,8 @@ import android.view.accessibility.AccessibilityEvent;
*
* &lt;GridView
* android:id="@id/content"
- * android:layout_width="fill_parent"
- * android:layout_height="fill_parent" /&gt;
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent" /&gt;
*
* &lt;/SlidingDrawer&gt;
* </pre>
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 31920e7..78e2fee 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -279,6 +279,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
if (!handled
&& (event.getAction() == KeyEvent.ACTION_DOWN)
&& (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP)
+ && (mCurrentView != null)
&& (mCurrentView.isRootNamespace())
&& (mCurrentView.hasFocus())
&& (mCurrentView.findFocus().focusSearch(View.FOCUS_UP) == null)) {
@@ -292,7 +293,9 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
- mCurrentView.dispatchWindowFocusChanged(hasFocus);
+ if (mCurrentView != null){
+ mCurrentView.dispatchWindowFocusChanged(hasFocus);
+ }
}
public void setCurrentTab(int index) {
@@ -324,8 +327,8 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
.addView(
mCurrentView,
new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT));
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
}
if (!mTabWidget.hasFocus()) {
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 2ba6268..aa47e6d 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -26,7 +24,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnFocusChangeListener;
@@ -183,7 +180,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
@Override
public void childDrawableStateChanged(View child) {
- if (child == getChildTabViewAt(mSelectedTab)) {
+ if (getTabCount() > 0 && child == getChildTabViewAt(mSelectedTab)) {
// To make sure that the bottom strip is redrawn
invalidate();
}
@@ -194,6 +191,9 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
+ // Do nothing if there are no tabs.
+ if (getTabCount() == 0) return;
+
// If the user specified a custom view for the tab indicators, then
// do not draw the bottom strips.
if (!mDrawBottomStrips) {
@@ -310,7 +310,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
if (child.getLayoutParams() == null) {
final LinearLayout.LayoutParams lp = new LayoutParams(
0,
- ViewGroup.LayoutParams.FILL_PARENT, 1.0f);
+ ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
lp.setMargins(0, 0, 0, 0);
child.setLayoutParams(lp);
}
@@ -325,7 +325,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
ImageView divider = new ImageView(mContext);
final LinearLayout.LayoutParams lp = new LayoutParams(
mDividerDrawable.getIntrinsicWidth(),
- LayoutParams.FILL_PARENT);
+ LayoutParams.MATCH_PARENT);
lp.setMargins(0, 0, 0, 0);
divider.setLayoutParams(lp);
divider.setBackgroundDrawable(mDividerDrawable);
@@ -347,7 +347,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
}
public void onFocusChange(View v, boolean hasFocus) {
- if (v == this && hasFocus) {
+ if (v == this && hasFocus && getTabCount() > 0) {
getChildTabViewAt(mSelectedTab).requestFocus();
return;
}
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index afa2f3b..66500a3 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -52,7 +52,7 @@ import java.util.regex.Pattern;
* {@link #setColumnCollapsed(int,boolean) setColumnCollapsed()}.</p>
*
* <p>The children of a TableLayout cannot specify the <code>layout_width</code>
- * attribute. Width is always <code>FILL_PARENT</code>. However, the
+ * attribute. Width is always <code>MATCH_PARENT</code>. However, the
* <code>layout_height</code> attribute can be defined by a child; default value
* is {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}. If the child
* is a {@link android.widget.TableRow}, then the height is always
@@ -621,7 +621,7 @@ public class TableLayout extends LinearLayout {
/**
* Returns a set of layout parameters with a width of
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
* and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
*/
@Override
@@ -647,7 +647,7 @@ public class TableLayout extends LinearLayout {
/**
* <p>This set of layout parameters enforces the width of each child to be
- * {@link #FILL_PARENT} and the height of each child to be
+ * {@link #MATCH_PARENT} and the height of each child to be
* {@link #WRAP_CONTENT}, but only if the height is not specified.</p>
*/
@SuppressWarnings({"UnusedDeclaration"})
@@ -663,14 +663,14 @@ public class TableLayout extends LinearLayout {
* {@inheritDoc}
*/
public LayoutParams(int w, int h) {
- super(FILL_PARENT, h);
+ super(MATCH_PARENT, h);
}
/**
* {@inheritDoc}
*/
public LayoutParams(int w, int h, float initWeight) {
- super(FILL_PARENT, h, initWeight);
+ super(MATCH_PARENT, h, initWeight);
}
/**
@@ -679,7 +679,7 @@ public class TableLayout extends LinearLayout {
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
*/
public LayoutParams() {
- super(FILL_PARENT, WRAP_CONTENT);
+ super(MATCH_PARENT, WRAP_CONTENT);
}
/**
@@ -698,7 +698,7 @@ public class TableLayout extends LinearLayout {
/**
* <p>Fixes the row's width to
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}; the row's
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}; the row's
* height is fixed to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} if no layout
* height is specified.</p>
@@ -710,7 +710,7 @@ public class TableLayout extends LinearLayout {
@Override
protected void setBaseAttributes(TypedArray a,
int widthAttr, int heightAttr) {
- this.width = FILL_PARENT;
+ this.width = MATCH_PARENT;
if (a.hasValue(heightAttr)) {
this.height = a.getLayoutDimension(heightAttr, "layout_height");
} else {
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index 5628cab..abf08bf 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -35,7 +35,7 @@ import android.view.ViewDebug;
* <p>The children of a TableRow do not need to specify the
* <code>layout_width</code> and <code>layout_height</code> attributes in the
* XML file. TableRow always enforces those values to be respectively
- * {@link android.widget.TableLayout.LayoutParams#FILL_PARENT} and
+ * {@link android.widget.TableLayout.LayoutParams#MATCH_PARENT} and
* {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p>
*
* <p>
@@ -299,7 +299,7 @@ public class TableRow extends LinearLayout {
case LayoutParams.WRAP_CONTENT:
spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
break;
- case LayoutParams.FILL_PARENT:
+ case LayoutParams.MATCH_PARENT:
spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
break;
default:
@@ -351,7 +351,7 @@ public class TableRow extends LinearLayout {
/**
* Returns a set of layout parameters with a width of
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
* a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
*/
@Override
@@ -451,7 +451,7 @@ public class TableRow extends LinearLayout {
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
*/
public LayoutParams() {
- super(FILL_PARENT, WRAP_CONTENT);
+ super(MATCH_PARENT, WRAP_CONTENT);
column = -1;
span = 1;
}
@@ -459,7 +459,7 @@ public class TableRow extends LinearLayout {
/**
* <p>Puts the view in the specified column.</p>
*
- * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}
+ * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
* and the child height to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
*
@@ -490,7 +490,7 @@ public class TableRow extends LinearLayout {
if (a.hasValue(widthAttr)) {
width = a.getLayoutDimension(widthAttr, "layout_width");
} else {
- width = FILL_PARENT;
+ width = MATCH_PARENT;
}
// We don't want to force users to specify a layout_height
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 455b593..12e8e29 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4992,7 +4992,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int height = mLayoutParams.height;
// If the size of the view does not depend on the size of the text, try to
// start the marquee immediately
- if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) {
+ if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
startMarquee();
} else {
// Defer the start of the marquee until we know our width (see setFrame())
@@ -5307,7 +5307,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (desiredHeight != this.getHeight()) {
sizeChanged = true;
}
- } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) {
+ } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
if (mDesiredHeightAtMeasure >= 0) {
int desiredHeight = getDesiredHeight();
@@ -5354,7 +5354,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
- mLayoutParams.height != LayoutParams.FILL_PARENT) {
+ mLayoutParams.height != LayoutParams.MATCH_PARENT) {
invalidate();
return;
}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index ab4edc5..b87e278 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -25,7 +25,7 @@ import android.view.LayoutInflater;
import android.view.View;
import com.android.internal.R;
-import com.android.internal.widget.NumberPicker;
+import com.android.common.widget.NumberPicker;
import java.text.DateFormatSymbols;
import java.util.Calendar;
@@ -357,4 +357,3 @@ public class TimePicker extends FrameLayout {
mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
}
}
-
diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java
index 0dcaf95..71ae624 100644
--- a/core/java/android/widget/ViewSwitcher.java
+++ b/core/java/android/widget/ViewSwitcher.java
@@ -80,7 +80,7 @@ public class ViewSwitcher extends ViewAnimator {
View child = mFactory.makeView();
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp == null) {
- lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
+ lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
addView(child, lp);
return child;
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index e55fbb8..bea009c 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -247,7 +247,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
LayoutParams.FLAG_LAYOUT_NO_LIMITS |
LayoutParams.FLAG_ALT_FOCUSABLE_IM;
lp.height = LayoutParams.WRAP_CONTENT;
- lp.width = LayoutParams.FILL_PARENT;
+ lp.width = LayoutParams.MATCH_PARENT;
lp.type = LayoutParams.TYPE_APPLICATION_PANEL;
lp.format = PixelFormat.TRANSLUCENT;
lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons;
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 57dbb44..f56b15c 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -16,7 +16,7 @@
package com.android.internal.app;
-import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -361,7 +361,7 @@ public class AlertController {
if (mView != null) {
customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
- custom.addView(mView, new LayoutParams(FILL_PARENT, FILL_PARENT));
+ custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
if (mViewSpacingSpecified) {
custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
@@ -391,7 +391,7 @@ public class AlertController {
if (mCustomTitleView != null) {
// Add the custom title view directly to the topPanel layout
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
topPanel.addView(mCustomTitleView, lp);
@@ -460,8 +460,8 @@ public class AlertController {
if (mListView != null) {
contentPanel.removeView(mWindow.findViewById(R.id.scrollView));
contentPanel.addView(mListView,
- new LinearLayout.LayoutParams(FILL_PARENT, FILL_PARENT));
- contentPanel.setLayoutParams(new LinearLayout.LayoutParams(FILL_PARENT, 0, 1.0f));
+ new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ contentPanel.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1.0f));
} else {
contentPanel.setVisibility(View.GONE);
}
@@ -657,8 +657,8 @@ public class AlertController {
ViewGroup parent = (ViewGroup) mWindow.findViewById(R.id.parentPanel);
parent.removeView(buttonPanel);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
- AbsListView.LayoutParams.FILL_PARENT,
- AbsListView.LayoutParams.FILL_PARENT);
+ AbsListView.LayoutParams.MATCH_PARENT,
+ AbsListView.LayoutParams.MATCH_PARENT);
buttonPanel.setLayoutParams(params);
mListView.addFooterView(buttonPanel);
*/
diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
index 000f6c4..2b07ae6 100644
--- a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
+++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
@@ -102,7 +102,7 @@ public class ExternalMediaFormatActivity extends AlertActivity implements Dialog
.getService("mount"));
if (mountService != null) {
try {
- mountService.formatMedia(Environment.getExternalStorageDirectory().toString());
+ mountService.formatVolume(Environment.getExternalStorageDirectory().toString());
} catch (RemoteException e) {
}
}
diff --git a/core/java/com/android/internal/database/ArrayListCursor.java b/core/java/com/android/internal/database/ArrayListCursor.java
deleted file mode 100644
index 2e1d8f1..0000000
--- a/core/java/com/android/internal/database/ArrayListCursor.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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 com.android.internal.database;
-
-import android.database.AbstractCursor;
-import android.database.CursorWindow;
-
-import java.lang.System;
-import java.util.ArrayList;
-
-/**
- * A convenience class that presents a two-dimensional ArrayList
- * as a Cursor.
- */
-public class ArrayListCursor extends AbstractCursor {
- private String[] mColumnNames;
- private ArrayList<Object>[] mRows;
-
- @SuppressWarnings({"unchecked"})
- public ArrayListCursor(String[] columnNames, ArrayList<ArrayList> rows) {
- int colCount = columnNames.length;
- boolean foundID = false;
- // Add an _id column if not in columnNames
- for (int i = 0; i < colCount; ++i) {
- if (columnNames[i].compareToIgnoreCase("_id") == 0) {
- mColumnNames = columnNames;
- foundID = true;
- break;
- }
- }
-
- if (!foundID) {
- mColumnNames = new String[colCount + 1];
- System.arraycopy(columnNames, 0, mColumnNames, 0, columnNames.length);
- mColumnNames[colCount] = "_id";
- }
-
- int rowCount = rows.size();
- mRows = new ArrayList[rowCount];
-
- for (int i = 0; i < rowCount; ++i) {
- mRows[i] = rows.get(i);
- if (!foundID) {
- mRows[i].add(i);
- }
- }
- }
-
- @Override
- public void fillWindow(int position, CursorWindow window) {
- if (position < 0 || position > getCount()) {
- return;
- }
-
- window.acquireReference();
- try {
- int oldpos = mPos;
- mPos = position - 1;
- window.clear();
- window.setStartPosition(position);
- int columnNum = getColumnCount();
- window.setNumColumns(columnNum);
- while (moveToNext() && window.allocRow()) {
- for (int i = 0; i < columnNum; i++) {
- final Object data = mRows[mPos].get(i);
- if (data != null) {
- if (data instanceof byte[]) {
- byte[] field = (byte[]) data;
- if (!window.putBlob(field, mPos, i)) {
- window.freeLastRow();
- break;
- }
- } else {
- String field = data.toString();
- if (!window.putString(field, mPos, i)) {
- window.freeLastRow();
- break;
- }
- }
- } else {
- if (!window.putNull(mPos, i)) {
- window.freeLastRow();
- break;
- }
- }
- }
- }
-
- mPos = oldpos;
- } catch (IllegalStateException e){
- // simply ignore it
- } finally {
- window.releaseReference();
- }
- }
-
- @Override
- public int getCount() {
- return mRows.length;
- }
-
- @Override
- public boolean deleteRow() {
- return false;
- }
-
- @Override
- public String[] getColumnNames() {
- return mColumnNames;
- }
-
- @Override
- public byte[] getBlob(int columnIndex) {
- return (byte[]) mRows[mPos].get(columnIndex);
- }
-
- @Override
- public String getString(int columnIndex) {
- Object cell = mRows[mPos].get(columnIndex);
- return (cell == null) ? null : cell.toString();
- }
-
- @Override
- public short getShort(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.shortValue();
- }
-
- @Override
- public int getInt(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.intValue();
- }
-
- @Override
- public long getLong(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.longValue();
- }
-
- @Override
- public float getFloat(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.floatValue();
- }
-
- @Override
- public double getDouble(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.doubleValue();
- }
-
- @Override
- public boolean isNull(int columnIndex) {
- return mRows[mPos].get(columnIndex) == null;
- }
-}
diff --git a/core/java/com/android/internal/logging/AndroidHandler.java b/core/java/com/android/internal/logging/AndroidHandler.java
index c4a1479..12f6a4f 100644
--- a/core/java/com/android/internal/logging/AndroidHandler.java
+++ b/core/java/com/android/internal/logging/AndroidHandler.java
@@ -17,12 +17,16 @@
package com.android.internal.logging;
import android.util.Log;
+import dalvik.system.DalvikLogging;
+import dalvik.system.DalvikLogHandler;
-import java.util.logging.*;
-import java.util.Date;
-import java.text.MessageFormat;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
/**
* Implements a {@link java.util.logging.Logger} handler that writes to the Android log. The
@@ -77,7 +81,7 @@ import java.io.StringWriter;
* </tr>
* </table>
*/
-public class AndroidHandler extends Handler {
+public class AndroidHandler extends Handler implements DalvikLogHandler {
/**
* Holds the formatter for all Android log handlers.
*/
@@ -118,33 +122,13 @@ public class AndroidHandler extends Handler {
@Override
public void publish(LogRecord record) {
- try {
- int level = getAndroidLevel(record.getLevel());
- String tag = record.getLoggerName();
-
- if (tag == null) {
- // Anonymous logger.
- tag = "null";
- } else {
- // Tags must be <= 23 characters.
- int length = tag.length();
- if (length > 23) {
- // Most loggers use the full class name. Try dropping the
- // package.
- int lastPeriod = tag.lastIndexOf(".");
- if (length - lastPeriod - 1 <= 23) {
- tag = tag.substring(lastPeriod + 1);
- } else {
- // Use last 23 chars.
- tag = tag.substring(tag.length() - 23);
- }
- }
- }
-
- if (!Log.isLoggable(tag, level)) {
- return;
- }
+ int level = getAndroidLevel(record.getLevel());
+ String tag = DalvikLogging.loggerNameToTag(record.getLoggerName());
+ if (!Log.isLoggable(tag, level)) {
+ return;
+ }
+ try {
String message = getFormatter().format(record);
Log.println(level, tag, message);
} catch (RuntimeException e) {
@@ -152,12 +136,26 @@ public class AndroidHandler extends Handler {
}
}
+ public void publish(Logger source, String tag, Level level, String message) {
+ // TODO: avoid ducking into native 2x; we aren't saving any formatter calls
+ int priority = getAndroidLevel(level);
+ if (!Log.isLoggable(tag, priority)) {
+ return;
+ }
+
+ try {
+ Log.println(priority, tag, message);
+ } catch (RuntimeException e) {
+ Log.e("AndroidHandler", "Error logging message.", e);
+ }
+ }
+
/**
* Converts a {@link java.util.logging.Logger} logging level into an Android one.
- *
+ *
* @param level The {@link java.util.logging.Logger} logging level.
- *
- * @return The resulting Android logging level.
+ *
+ * @return The resulting Android logging level.
*/
static int getAndroidLevel(Level level) {
int value = level.intValue();
@@ -171,5 +169,4 @@ public class AndroidHandler extends Handler {
return Log.DEBUG;
}
}
-
}
diff --git a/core/java/com/android/internal/net/DbSSLSessionCache.java b/core/java/com/android/internal/net/DbSSLSessionCache.java
deleted file mode 100644
index 842d40b..0000000
--- a/core/java/com/android/internal/net/DbSSLSessionCache.java
+++ /dev/null
@@ -1,289 +0,0 @@
-// Copyright 2009 The Android Open Source Project
-
-package com.android.internal.net;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.net.ssl.SSLSession;
-
-/**
- * Hook into harmony SSL cache to persist the SSL sessions.
- *
- * Current implementation is suitable for saving a small number of hosts -
- * like google services. It can be extended with expiration and more features
- * to support more hosts.
- *
- * {@hide}
- */
-public class DbSSLSessionCache implements SSLClientSessionCache {
- private static final String TAG = "DbSSLSessionCache";
-
- /**
- * Table where sessions are stored.
- */
- public static final String SSL_CACHE_TABLE = "ssl_sessions";
-
- private static final String SSL_CACHE_ID = "_id";
-
- /**
- * Key is host:port - port is not optional.
- */
- private static final String SSL_CACHE_HOSTPORT = "hostport";
-
- /**
- * Base64-encoded DER value of the session.
- */
- private static final String SSL_CACHE_SESSION = "session";
-
- /**
- * Time when the record was added - should be close to the time
- * of the initial session negotiation.
- */
- private static final String SSL_CACHE_TIME_SEC = "time_sec";
-
- public static final String DATABASE_NAME = "ssl_sessions.db";
-
- public static final int DATABASE_VERSION = 2;
-
- /** public for testing
- */
- public static final int SSL_CACHE_ID_COL = 0;
- public static final int SSL_CACHE_HOSTPORT_COL = 1;
- public static final int SSL_CACHE_SESSION_COL = 2;
- public static final int SSL_CACHE_TIME_SEC_COL = 3;
-
- public static final int MAX_CACHE_SIZE = 256;
-
- private final Map<String, byte[]> mExternalCache =
- new HashMap<String, byte[]>();
-
-
- private DatabaseHelper mDatabaseHelper;
-
- private boolean mNeedsCacheLoad = true;
-
- public static final String[] PROJECTION = new String[] {
- SSL_CACHE_ID,
- SSL_CACHE_HOSTPORT,
- SSL_CACHE_SESSION,
- SSL_CACHE_TIME_SEC
- };
-
- private static final Map<String,DbSSLSessionCache> sInstances =
- new HashMap<String,DbSSLSessionCache>();
-
- /**
- * Returns a singleton instance of the DbSSLSessionCache that should be used for this
- * context's package.
- *
- * @param context The context that should be used for getting/creating the singleton instance.
- * @return The singleton instance for the context's package.
- */
- public static synchronized DbSSLSessionCache getInstanceForPackage(Context context) {
- String packageName = context.getPackageName();
- if (sInstances.containsKey(packageName)) {
- return sInstances.get(packageName);
- }
- DbSSLSessionCache cache = new DbSSLSessionCache(context);
- sInstances.put(packageName, cache);
- return cache;
- }
-
- /**
- * Create a SslSessionCache instance, using the specified context to
- * initialize the database.
- *
- * This constructor will use the default database - created for the application
- * context.
- *
- * @param activityContext
- */
- private DbSSLSessionCache(Context activityContext) {
- Context appContext = activityContext.getApplicationContext();
- mDatabaseHelper = new DatabaseHelper(appContext);
- }
-
- /**
- * Create a SslSessionCache that uses a specific database.
- *
- *
- * @param database
- */
- public DbSSLSessionCache(DatabaseHelper database) {
- this.mDatabaseHelper = database;
- }
-
- public void putSessionData(SSLSession session, byte[] der) {
- if (mDatabaseHelper == null) {
- return;
- }
- synchronized (this.getClass()) {
- SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
- if (mExternalCache.size() == MAX_CACHE_SIZE) {
- // remove oldest.
- // TODO: check if the new one is in cached already ( i.e. update ).
- Cursor byTime = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE,
- PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC);
- if (byTime.moveToFirst()) {
- // TODO: can I do byTime.deleteRow() ?
- String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL);
- db.delete(SSL_CACHE_TABLE,
- SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort });
- mExternalCache.remove(hostPort);
- } else {
- Log.w(TAG, "No rows found");
- // something is wrong, clear it
- clear();
- }
- }
- // Serialize native session to standard DER encoding
- long t0 = System.currentTimeMillis();
-
- String b64 = new String(Base64.encodeBase64(der));
- String key = session.getPeerHost() + ":" + session.getPeerPort();
-
- ContentValues values = new ContentValues();
- values.put(SSL_CACHE_HOSTPORT, key);
- values.put(SSL_CACHE_SESSION, b64);
- values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000);
-
- mExternalCache.put(key, der);
-
- try {
- db.insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values);
- } catch(SQLException ex) {
- // Ignore - nothing we can do to recover, and caller shouldn't
- // be affected.
- Log.w(TAG, "Ignoring SQL exception when caching session", ex);
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- long t1 = System.currentTimeMillis();
- Log.d(TAG, "New SSL session " + session.getPeerHost() +
- " DER len: " + der.length + " " + (t1 - t0));
- }
- }
-
- }
-
- public byte[] getSessionData(String host, int port) {
- // Current (simple) implementation does a single lookup to DB, then saves
- // all entries to the cache.
-
- // This works for google services - i.e. small number of certs.
- // If we extend this to all processes - we should hold a separate cache
- // or do lookups to DB each time.
- if (mDatabaseHelper == null) {
- return null;
- }
- synchronized(this.getClass()) {
- if (mNeedsCacheLoad) {
- // Don't try to load again, if something is wrong on the first
- // request it'll likely be wrong each time.
- mNeedsCacheLoad = false;
- long t0 = System.currentTimeMillis();
-
- Cursor cur = null;
- try {
- cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE,
- PROJECTION, null, null, null, null, null);
- if (cur.moveToFirst()) {
- do {
- String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL);
- String value = cur.getString(SSL_CACHE_SESSION_COL);
-
- if (hostPort == null || value == null) {
- continue;
- }
- // TODO: blob support ?
- byte[] der = Base64.decodeBase64(value.getBytes());
- mExternalCache.put(hostPort, der);
- } while (cur.moveToNext());
-
- }
- } catch (SQLException ex) {
- Log.d(TAG, "Error loading SSL cached entries ", ex);
- } finally {
- if (cur != null) {
- cur.close();
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- long t1 = System.currentTimeMillis();
- Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms");
- }
- }
- }
-
- String key = host + ":" + port;
-
- return mExternalCache.get(key);
- }
- }
-
- /**
- * Reset the database and internal state.
- * Used for testing or to free space.
- */
- public void clear() {
- synchronized(this) {
- try {
- mExternalCache.clear();
- mNeedsCacheLoad = true;
- mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE,
- null, null);
- } catch (SQLException ex) {
- Log.d(TAG, "Error removing SSL cached entries ", ex);
- // ignore - nothing we can do about it
- }
- }
- }
-
- public byte[] getSessionData(byte[] id) {
- // We support client side only - the cache will do nothing for
- // server-side sessions.
- return null;
- }
-
- /** Visible for testing.
- */
- public static class DatabaseHelper extends SQLiteOpenHelper {
-
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + SSL_CACHE_TABLE + " (" +
- SSL_CACHE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- SSL_CACHE_HOSTPORT + " TEXT UNIQUE ON CONFLICT REPLACE," +
- SSL_CACHE_SESSION + " TEXT," +
- SSL_CACHE_TIME_SEC + " INTEGER" +
- ");");
-
- // No index - we load on startup, index would slow down inserts.
- // If we want to scale this to lots of rows - we could use
- // index, but then we'll hit DB a bit too often ( including
- // negative hits )
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL("DROP TABLE IF EXISTS " + SSL_CACHE_TABLE );
- onCreate(db);
- }
-
- }
-
-}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5199ada..e964a8f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -17,8 +17,8 @@
package com.android.internal.os;
import android.bluetooth.BluetoothHeadset;
+import android.net.TrafficStats;
import android.os.BatteryStats;
-import android.os.NetStat;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
@@ -1022,8 +1022,8 @@ public final class BatteryStatsImpl extends BatteryStats {
public void doUnplug(long batteryUptime, long batteryRealtime) {
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
Uid u = mUidStats.valueAt(iu);
- u.mStartedTcpBytesReceived = NetStat.getUidRxBytes(u.mUid);
- u.mStartedTcpBytesSent = NetStat.getUidTxBytes(u.mUid);
+ u.mStartedTcpBytesReceived = TrafficStats.getUidRxBytes(u.mUid);
+ u.mStartedTcpBytesSent = TrafficStats.getUidTxBytes(u.mUid);
u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent;
}
@@ -1031,10 +1031,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
}
// Track total mobile data
- doDataUnplug(mMobileDataRx, NetStat.getMobileRxBytes());
- doDataUnplug(mMobileDataTx, NetStat.getMobileTxBytes());
- doDataUnplug(mTotalDataRx, NetStat.getTotalRxBytes());
- doDataUnplug(mTotalDataTx, NetStat.getTotalTxBytes());
+ doDataUnplug(mMobileDataRx, TrafficStats.getMobileRxBytes());
+ doDataUnplug(mMobileDataTx, TrafficStats.getMobileTxBytes());
+ doDataUnplug(mTotalDataRx, TrafficStats.getTotalRxBytes());
+ doDataUnplug(mTotalDataTx, TrafficStats.getTotalTxBytes());
// Track radio awake time
mRadioDataStart = getCurrentRadioDataUptime();
mRadioDataUptime = 0;
@@ -1058,10 +1058,10 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
}
- doDataPlug(mMobileDataRx, NetStat.getMobileRxBytes());
- doDataPlug(mMobileDataTx, NetStat.getMobileTxBytes());
- doDataPlug(mTotalDataRx, NetStat.getTotalRxBytes());
- doDataPlug(mTotalDataTx, NetStat.getTotalTxBytes());
+ doDataPlug(mMobileDataRx, TrafficStats.getMobileRxBytes());
+ doDataPlug(mMobileDataTx, TrafficStats.getMobileTxBytes());
+ doDataPlug(mTotalDataRx, TrafficStats.getTotalRxBytes());
+ doDataPlug(mTotalDataTx, TrafficStats.getTotalTxBytes());
// Track radio awake time
mRadioDataUptime = getRadioDataUptime();
mRadioDataStart = -1;
@@ -1519,7 +1519,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public long computeCurrentTcpBytesReceived() {
return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
- ? (NetStat.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
+ ? (TrafficStats.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
}
@Override
@@ -1696,7 +1696,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public long computeCurrentTcpBytesSent() {
return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
- ? (NetStat.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
+ ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
}
void writeToParcelLocked(Parcel out, long batteryRealtime) {
@@ -2919,22 +2919,22 @@ public final class BatteryStatsImpl extends BatteryStats {
/** Only STATS_UNPLUGGED works properly */
public long getMobileTcpBytesSent(int which) {
- return getTcpBytes(NetStat.getMobileTxBytes(), mMobileDataTx, which);
+ return getTcpBytes(TrafficStats.getMobileTxBytes(), mMobileDataTx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getMobileTcpBytesReceived(int which) {
- return getTcpBytes(NetStat.getMobileRxBytes(), mMobileDataRx, which);
+ return getTcpBytes(TrafficStats.getMobileRxBytes(), mMobileDataRx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getTotalTcpBytesSent(int which) {
- return getTcpBytes(NetStat.getTotalTxBytes(), mTotalDataTx, which);
+ return getTcpBytes(TrafficStats.getTotalTxBytes(), mTotalDataTx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getTotalTcpBytesReceived(int which) {
- return getTcpBytes(NetStat.getTotalRxBytes(), mTotalDataRx, which);
+ return getTcpBytes(TrafficStats.getTotalRxBytes(), mTotalDataRx, which);
}
@Override
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index eacf0ce..ba0bf0d 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -76,6 +76,13 @@ public class BinderInternal {
*/
public static final native IBinder getContextObject();
+ /**
+ * Special for system process to not allow incoming calls to run at
+ * background scheduling priority.
+ * @hide
+ */
+ public static final native void disableBackgroundScheduling(boolean disable);
+
static native final void handleGc();
public static void forceGc(String reason) {
diff --git a/core/java/com/android/internal/os/IDropBoxManagerService.aidl b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
new file mode 100644
index 0000000..d067926
--- /dev/null
+++ b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.os;
+
+import android.os.DropBoxManager;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * "Backend" interface used by {@link android.os.DropBoxManager} to talk to the
+ * DropBoxManagerService that actually implements the drop box functionality.
+ *
+ * @see DropBoxManager
+ * @hide
+ */
+interface IDropBoxManagerService {
+ /**
+ * @see DropBoxManager#addText
+ * @see DropBoxManager#addData
+ * @see DropBoxManager#addFile
+ */
+ void add(in DropBoxManager.Entry entry);
+
+ /** @see DropBoxManager#getNextEntry */
+ boolean isTagEnabled(String tag);
+
+ /** @see DropBoxManager#getNextEntry */
+ DropBoxManager.Entry getNextEntry(String tag, long millis);
+}
diff --git a/core/java/com/android/internal/os/LoggingPrintStream.java b/core/java/com/android/internal/os/LoggingPrintStream.java
index b3d6f20..451340b 100644
--- a/core/java/com/android/internal/os/LoggingPrintStream.java
+++ b/core/java/com/android/internal/os/LoggingPrintStream.java
@@ -16,11 +16,17 @@
package com.android.internal.os;
-import java.io.PrintStream;
-import java.io.OutputStream;
import java.io.IOException;
-import java.util.Locale;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
import java.util.Formatter;
+import java.util.Locale;
/**
* A print stream which logs output line by line.
@@ -31,6 +37,27 @@ abstract class LoggingPrintStream extends PrintStream {
private final StringBuilder builder = new StringBuilder();
+ /**
+ * A buffer that is initialized when raw bytes are first written to this
+ * stream. It may contain the leading bytes of multi-byte characters.
+ * Between writes this buffer is always ready to receive data; ie. the
+ * position is at the first unassigned byte and the limit is the capacity.
+ */
+ private ByteBuffer encodedBytes;
+
+ /**
+ * A buffer that is initialized when raw bytes are first written to this
+ * stream. Between writes this buffer is always clear; ie. the position is
+ * zero and the limit is the capacity.
+ */
+ private CharBuffer decodedChars;
+
+ /**
+ * Decodes bytes to characters using the system default charset. Initialized
+ * when raw bytes are first written to this stream.
+ */
+ private CharsetDecoder decoder;
+
protected LoggingPrintStream() {
super(new OutputStream() {
public void write(int oneByte) throws IOException {
@@ -80,20 +107,48 @@ abstract class LoggingPrintStream extends PrintStream {
}
}
- /*
- * We have no idea of how these bytes are encoded, so just ignore them.
- */
-
- /** Ignored. */
- public void write(int oneByte) {}
+ public void write(int oneByte) {
+ write(new byte[] { (byte) oneByte }, 0, 1);
+ }
- /** Ignored. */
@Override
- public void write(byte buffer[]) {}
+ public void write(byte[] buffer) {
+ write(buffer, 0, buffer.length);
+ }
- /** Ignored. */
@Override
- public void write(byte bytes[], int start, int count) {}
+ public synchronized void write(byte bytes[], int start, int count) {
+ if (decoder == null) {
+ encodedBytes = ByteBuffer.allocate(80);
+ decodedChars = CharBuffer.allocate(80);
+ decoder = Charset.defaultCharset().newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE);
+ }
+
+ int end = start + count;
+ while (start < end) {
+ // copy some bytes from the array to the long-lived buffer. This
+ // way, if we end with a partial character we don't lose it.
+ int numBytes = Math.min(encodedBytes.remaining(), end - start);
+ encodedBytes.put(bytes, start, numBytes);
+ start += numBytes;
+
+ encodedBytes.flip();
+ CoderResult coderResult;
+ do {
+ // decode bytes from the byte buffer into the char buffer
+ coderResult = decoder.decode(encodedBytes, decodedChars, false);
+
+ // copy chars from the char buffer into our string builder
+ decodedChars.flip();
+ builder.append(decodedChars);
+ decodedChars.clear();
+ } while (coderResult.isOverflow());
+ encodedBytes.compact();
+ }
+ flush(false);
+ }
/** Always returns false. */
@Override
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 2369d25..9e5bdff 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -20,7 +20,7 @@ package com.android.internal.os;
import android.content.Context;
import android.content.res.XmlResourceParser;
-import com.android.internal.util.XmlUtils;
+import com.android.common.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/com/android/internal/os/RecoverySystem.java b/core/java/com/android/internal/os/RecoverySystem.java
deleted file mode 100644
index c938610..0000000
--- a/core/java/com/android/internal/os/RecoverySystem.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.internal.os;
-
-import android.os.FileUtils;
-import android.os.Power;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Utility class for interacting with the Android recovery partition.
- * The recovery partition is a small standalone system which can perform
- * operations that are difficult while the main system is running, like
- * upgrading system software or reformatting the data partition.
- * Note that most of these operations must be run as root.
- *
- * @hide
- */
-public class RecoverySystem {
- private static final String TAG = "RecoverySystem"; // for logging
-
- // Used to communicate with recovery. See commands/recovery/recovery.c.
- private static File RECOVERY_DIR = new File("/cache/recovery");
- private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
- private static File LOG_FILE = new File(RECOVERY_DIR, "log");
-
- // Length limits for reading files.
- private static int LOG_FILE_MAX_LENGTH = 8 * 1024;
-
- /**
- * Reboot into the recovery system to install a system update.
- * @param update package to install (must be in /cache or /data).
- * @throws IOException if something goes wrong.
- */
- public static void rebootAndUpdate(File update) throws IOException {
- String path = update.getCanonicalPath();
- if (path.startsWith("/cache/")) {
- path = "CACHE:" + path.substring(7);
- } else if (path.startsWith("/data/")) {
- path = "DATA:" + path.substring(6);
- } else {
- throw new IllegalArgumentException(
- "Must start with /cache or /data: " + path);
- }
- bootCommand("--update_package=" + path);
- }
-
- /**
- * Reboot into the recovery system to wipe the /data partition.
- * @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
- * @throws IOException if something goes wrong.
- */
- public static void rebootAndWipe() throws IOException {
- bootCommand("--wipe_data");
- }
-
- /**
- * Reboot into the recovery system with the supplied argument.
- * @param arg to pass to the recovery utility.
- * @throws IOException if something goes wrong.
- */
- private static void bootCommand(String arg) throws IOException {
- RECOVERY_DIR.mkdirs(); // In case we need it
- COMMAND_FILE.delete(); // In case it's not writable
- LOG_FILE.delete();
-
- FileWriter command = new FileWriter(COMMAND_FILE);
- try {
- command.write(arg);
- command.write("\n");
- } finally {
- command.close();
- }
-
- // Having written the command file, go ahead and reboot
- Power.reboot("recovery");
- throw new IOException("Reboot failed (no permissions?)");
- }
-
- /**
- * Called after booting to process and remove recovery-related files.
- * @return the log file from recovery, or null if none was found.
- */
- public static String handleAftermath() {
- // Record the tail of the LOG_FILE
- String log = null;
- try {
- log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
- } catch (FileNotFoundException e) {
- Log.i(TAG, "No recovery log file");
- } catch (IOException e) {
- Log.e(TAG, "Error reading recovery log", e);
- }
-
- // Delete everything in RECOVERY_DIR
- String[] names = RECOVERY_DIR.list();
- for (int i = 0; names != null && i < names.length; i++) {
- File f = new File(RECOVERY_DIR, names[i]);
- if (!f.delete()) {
- Log.e(TAG, "Can't delete: " + f);
- } else {
- Log.i(TAG, "Deleted: " + f);
- }
- }
-
- return log;
- }
-}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index c782c8c..57a28e6 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -17,7 +17,9 @@
package com.android.internal.os;
import android.app.ActivityManagerNative;
+import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
+import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
import android.os.ICheckinService;
@@ -25,8 +27,6 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.os.Build;
-import android.server.data.CrashData;
import android.util.Config;
import android.util.Log;
@@ -58,6 +58,10 @@ public class RuntimeInit {
/** true if commonInit() has been called */
private static boolean initialized;
+ private static IBinder mApplicationObject;
+
+ private static volatile boolean mCrashing = false;
+
/**
* Use this to log a message when a thread exits due to an uncaught
* exception. The framework catches these for the main threads, so
@@ -66,14 +70,30 @@ public class RuntimeInit {
private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
try {
- Log.e(TAG, "Uncaught handler: thread " + t.getName()
- + " exiting due to uncaught exception");
- } catch (Throwable error) {
- // Ignore the throwable, since we're in the process of crashing anyway.
- // If we don't, the crash won't happen properly and the process will
- // be left around in a bad state.
+ // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
+ if (mCrashing) return;
+ mCrashing = true;
+
+ if (mApplicationObject == null) {
+ Log.e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
+ } else {
+ Log.e(TAG, "FATAL EXCEPTION: " + t.getName(), e);
+ }
+
+ // Bring up crash dialog, wait for it to be dismissed
+ ActivityManagerNative.getDefault().handleApplicationCrash(
+ mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
+ } catch (Throwable t2) {
+ try {
+ Log.e(TAG, "Error reporting crash", t2);
+ } catch (Throwable t3) {
+ // Even Log.e() fails! Oh well.
+ }
+ } finally {
+ // Try everything to make sure this process goes away.
+ Process.killProcess(Process.myPid());
+ System.exit(10);
}
- crash(TAG, e);
}
}
@@ -300,80 +320,22 @@ public class RuntimeInit {
public static native int getQwertyKeyboard();
/**
- * Report a fatal error in the current process. If this is a user-process,
- * a dialog may be displayed informing the user of the error. This
- * function does not return; it forces the current process to exit.
+ * Report a serious error in the current process. May or may not cause
+ * the process to terminate (depends on system settings).
*
- * @param tag to use when logging the error
- * @param t exception that was generated by the error
+ * @param tag to record with the error
+ * @param t exception describing the error site and conditions
*/
- public static void crash(String tag, Throwable t) {
- if (mApplicationObject != null) {
- byte[] crashData = null;
- try {
- // Log exception.
- Log.e(TAG, Log.getStackTraceString(t));
- crashData = marshallException(tag, t);
- if (crashData == null) {
- throw new NullPointerException("Can't marshall crash data");
- }
- } catch (Throwable t2) {
- try {
- // Log exception as a string so we don't get in an infinite loop.
- Log.e(TAG, "Error reporting crash: "
- + Log.getStackTraceString(t2));
- } catch (Throwable t3) {
- // Do nothing, must be OOM so we can't format the message
- }
- }
-
- try {
- // Display user-visible error message.
- String msg = t.getMessage();
- if (msg == null) {
- msg = t.toString();
- }
-
- IActivityManager am = ActivityManagerNative.getDefault();
- try {
- int res = am.handleApplicationError(mApplicationObject,
- 0, tag, msg, t.toString(), crashData);
- // Is waiting for the debugger the right thing?
- // For now I have turned off the Debug button, because
- // I'm not sure what we should do if it is actually
- // selected.
- //Log.i(TAG, "Got app error result: " + res);
- if (res == 1) {
- Debug.waitForDebugger();
- return;
- }
- } catch (RemoteException e) {
- }
- } catch (Throwable t2) {
- try {
- // Log exception as a string so we don't get in an infinite loop.
- Log.e(TAG, "Error reporting crash: "
- + Log.getStackTraceString(t2));
- } catch (Throwable t3) {
- // Do nothing, must be OOM so we can't format the message
- }
- } finally {
- // Try everything to make sure this process goes away.
- Process.killProcess(Process.myPid());
- System.exit(10);
- }
- } else {
- try {
- Log.e(TAG, "*** EXCEPTION IN SYSTEM PROCESS. System will crash.");
- Log.e(tag, Log.getStackTraceString(t));
- reportException(tag, t, true); // synchronous
- } catch (Throwable t2) {
- // Do nothing, must be OOM so we can't format the message
- } finally {
- // Try everything to make sure this process goes away.
+ public static void wtf(String tag, Throwable t) {
+ try {
+ if (ActivityManagerNative.getDefault().handleApplicationWtf(
+ mApplicationObject, tag, new ApplicationErrorReport.CrashInfo(t))) {
+ // The Activity Manager has already written us off -- now exit.
Process.killProcess(Process.myPid());
System.exit(10);
}
+ } catch (Throwable t2) {
+ Log.e(TAG, "Error reporting WTF", t2);
}
}
@@ -381,82 +343,6 @@ public class RuntimeInit {
private static final AtomicInteger sInReportException = new AtomicInteger();
/**
- * Report an error in the current process. The exception information will
- * be handed off to the checkin service and eventually uploaded for analysis.
- * This is expensive! Only use this when the exception indicates a programming
- * error ("should not happen").
- *
- * @param tag to use when logging the error
- * @param t exception that was generated by the error
- * @param sync true to wait for the report, false to "fire and forget"
- */
- public static void reportException(String tag, Throwable t, boolean sync) {
- if (!initialized) {
- // Exceptions during, eg, zygote cannot use this mechanism
- return;
- }
-
- // It's important to prevent an infinite crash-reporting loop:
- // while this function is running, don't let it be called again.
- int reenter = sInReportException.getAndIncrement();
- if (reenter != 0) {
- sInReportException.decrementAndGet();
- Log.e(TAG, "Crash logging skipped, already logging another crash");
- return;
- }
-
- // TODO: Enable callers to specify a level (i.e. warn or error).
- try {
- // Submit crash data to statistics service.
- byte[] crashData = marshallException(tag, t);
- ICheckinService checkin = ICheckinService.Stub.asInterface(
- ServiceManager.getService("checkin"));
- if (checkin == null) {
- Log.e(TAG, "Crash logging skipped, no checkin service");
- } else if (sync) {
- checkin.reportCrashSync(crashData);
- } else {
- checkin.reportCrashAsync(crashData);
- }
- } catch (Throwable t2) {
- // Log exception as a string so we don't get in an infinite loop.
- Log.e(TAG, "Crash logging failed: " + t2);
- } finally {
- sInReportException.decrementAndGet();
- }
- }
-
- private static byte[] marshallException(String tag, Throwable t) {
- // Convert crash data to bytes.
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- DataOutputStream dout = new DataOutputStream(bout);
- try {
- new CrashData(tag, t).write(dout);
- dout.close();
- } catch (IOException e) {
- return null;
- }
- return bout.toByteArray();
- }
-
- /**
- * Replay an encoded CrashData record back into a useable CrashData record. This can be
- * helpful for providing debugging output after a process error.
- *
- * @param crashDataBytes The byte array containing the encoded crash record
- * @return new CrashData record, or null if could not create one.
- */
- public static CrashData unmarshallException(byte[] crashDataBytes) {
- try {
- ByteArrayInputStream bin = new ByteArrayInputStream(crashDataBytes);
- DataInputStream din = new DataInputStream(bin);
- return new CrashData(din);
- } catch (IOException e) {
- return null;
- }
- }
-
- /**
* Set the object identifying this application/process, for reporting VM
* errors.
*/
@@ -471,7 +357,4 @@ public class RuntimeInit {
// Register handlers for DDM messages.
android.ddm.DdmRegister.registerHandlers();
}
-
- private static IBinder mApplicationObject;
-
}
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
deleted file mode 100644
index 592a8fa..0000000
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * 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 com.android.internal.util;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CoderResult;
-import java.nio.charset.IllegalCharsetNameException;
-import java.nio.charset.UnsupportedCharsetException;
-
-/**
- * This is a quick and dirty implementation of XmlSerializer that isn't horribly
- * painfully slow like the normal one. It only does what is needed for the
- * specific XML files being written with it.
- */
-public class FastXmlSerializer implements XmlSerializer {
- private static final String ESCAPE_TABLE[] = new String[] {
- null, null, null, null, null, null, null, null, // 0-7
- null, null, null, null, null, null, null, null, // 8-15
- null, null, null, null, null, null, null, null, // 16-23
- null, null, null, null, null, null, null, null, // 24-31
- null, null, "&quot;", null, null, null, "&amp;", null, // 32-39
- null, null, null, null, null, null, null, null, // 40-47
- null, null, null, null, null, null, null, null, // 48-55
- null, null, null, null, "&lt;", null, "&gt;", null, // 56-63
- };
-
- private static final int BUFFER_LEN = 8192;
-
- private final char[] mText = new char[BUFFER_LEN];
- private int mPos;
-
- private Writer mWriter;
-
- private OutputStream mOutputStream;
- private CharsetEncoder mCharset;
- private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
-
- private boolean mInTag;
-
- private void append(char c) throws IOException {
- int pos = mPos;
- if (pos >= (BUFFER_LEN-1)) {
- flush();
- pos = mPos;
- }
- mText[pos] = c;
- mPos = pos+1;
- }
-
- private void append(String str, int i, final int length) throws IOException {
- if (length > BUFFER_LEN) {
- final int end = i + length;
- while (i < end) {
- int next = i + BUFFER_LEN;
- append(str, i, next<end ? BUFFER_LEN : (end-i));
- i = next;
- }
- return;
- }
- int pos = mPos;
- if ((pos+length) > BUFFER_LEN) {
- flush();
- pos = mPos;
- }
- str.getChars(i, i+length, mText, pos);
- mPos = pos + length;
- }
-
- private void append(char[] buf, int i, final int length) throws IOException {
- if (length > BUFFER_LEN) {
- final int end = i + length;
- while (i < end) {
- int next = i + BUFFER_LEN;
- append(buf, i, next<end ? BUFFER_LEN : (end-i));
- i = next;
- }
- return;
- }
- int pos = mPos;
- if ((pos+length) > BUFFER_LEN) {
- flush();
- pos = mPos;
- }
- System.arraycopy(buf, i, mText, pos, length);
- mPos = pos + length;
- }
-
- private void append(String str) throws IOException {
- append(str, 0, str.length());
- }
-
- private void escapeAndAppendString(final String string) throws IOException {
- final int N = string.length();
- final char NE = (char)ESCAPE_TABLE.length;
- final String[] escapes = ESCAPE_TABLE;
- int lastPos = 0;
- int pos;
- for (pos=0; pos<N; pos++) {
- char c = string.charAt(pos);
- if (c >= NE) continue;
- String escape = escapes[c];
- if (escape == null) continue;
- if (lastPos < pos) append(string, lastPos, pos-lastPos);
- lastPos = pos + 1;
- append(escape);
- }
- if (lastPos < pos) append(string, lastPos, pos-lastPos);
- }
-
- private void escapeAndAppendString(char[] buf, int start, int len) throws IOException {
- final char NE = (char)ESCAPE_TABLE.length;
- final String[] escapes = ESCAPE_TABLE;
- int end = start+len;
- int lastPos = start;
- int pos;
- for (pos=start; pos<end; pos++) {
- char c = buf[pos];
- if (c >= NE) continue;
- String escape = escapes[c];
- if (escape == null) continue;
- if (lastPos < pos) append(buf, lastPos, pos-lastPos);
- lastPos = pos + 1;
- append(escape);
- }
- if (lastPos < pos) append(buf, lastPos, pos-lastPos);
- }
-
- public XmlSerializer attribute(String namespace, String name, String value) throws IOException,
- IllegalArgumentException, IllegalStateException {
- append(' ');
- if (namespace != null) {
- append(namespace);
- append(':');
- }
- append(name);
- append("=\"");
-
- escapeAndAppendString(value);
- append('"');
- return this;
- }
-
- public void cdsect(String text) throws IOException, IllegalArgumentException,
- IllegalStateException {
- throw new UnsupportedOperationException();
- }
-
- public void comment(String text) throws IOException, IllegalArgumentException,
- IllegalStateException {
- throw new UnsupportedOperationException();
- }
-
- public void docdecl(String text) throws IOException, IllegalArgumentException,
- IllegalStateException {
- throw new UnsupportedOperationException();
- }
-
- public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException {
- flush();
- }
-
- public XmlSerializer endTag(String namespace, String name) throws IOException,
- IllegalArgumentException, IllegalStateException {
- if (mInTag) {
- append(" />\n");
- } else {
- append("</");
- if (namespace != null) {
- append(namespace);
- append(':');
- }
- append(name);
- append(">\n");
- }
- mInTag = false;
- return this;
- }
-
- public void entityRef(String text) throws IOException, IllegalArgumentException,
- IllegalStateException {
- throw new UnsupportedOperationException();
- }
-
- private void flushBytes() throws IOException {
- int position;
- if ((position = mBytes.position()) > 0) {
- mBytes.flip();
- mOutputStream.write(mBytes.array(), 0, position);
- mBytes.clear();
- }
- }
-
- public void flush() throws IOException {
- //Log.i("PackageManager", "flush mPos=" + mPos);
- if (mPos > 0) {
- if (mOutputStream != null) {
- CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
- CoderResult result = mCharset.encode(charBuffer, mBytes, true);
- while (true) {
- if (result.isError()) {
- throw new IOException(result.toString());
- } else if (result.isOverflow()) {
- flushBytes();
- result = mCharset.encode(charBuffer, mBytes, true);
- continue;
- }
- break;
- }
- flushBytes();
- mOutputStream.flush();
- } else {
- mWriter.write(mText, 0, mPos);
- mWriter.flush();
- }
- mPos = 0;
- }
- }
-
- public int getDepth() {
- throw new UnsupportedOperationException();
- }
-
- public boolean getFeature(String name) {
- throw new UnsupportedOperationException();
- }
-
- public String getName() {
- throw new UnsupportedOperationException();
- }
-
- public String getNamespace() {
- throw new UnsupportedOperationException();
- }
-
- public String getPrefix(String namespace, boolean generatePrefix)
- throws IllegalArgumentException {
- throw new UnsupportedOperationException();
- }
-
- public Object getProperty(String name) {
- throw new UnsupportedOperationException();
- }
-
- public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException,
- IllegalStateException {
- throw new UnsupportedOperationException();
- }
-
- public void processingInstruction(String text) throws IOException, IllegalArgumentException,
- IllegalStateException {
- throw new UnsupportedOperationException();
- }
-
- public void setFeature(String name, boolean state) throws IllegalArgumentException,
- IllegalStateException {
- if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
- return;
- }
- throw new UnsupportedOperationException();
- }
-
- public void setOutput(OutputStream os, String encoding) throws IOException,
- IllegalArgumentException, IllegalStateException {
- if (os == null)
- throw new IllegalArgumentException();
- if (true) {
- try {
- mCharset = Charset.forName(encoding).newEncoder();
- } catch (IllegalCharsetNameException e) {
- throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
- encoding).initCause(e));
- } catch (UnsupportedCharsetException e) {
- throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
- encoding).initCause(e));
- }
- mOutputStream = os;
- } else {
- setOutput(
- encoding == null
- ? new OutputStreamWriter(os)
- : new OutputStreamWriter(os, encoding));
- }
- }
-
- public void setOutput(Writer writer) throws IOException, IllegalArgumentException,
- IllegalStateException {
- mWriter = writer;
- }
-
- public void setPrefix(String prefix, String namespace) throws IOException,
- IllegalArgumentException, IllegalStateException {
- throw new UnsupportedOperationException();
- }
-
- public void setProperty(String name, Object value) throws IllegalArgumentException,
- IllegalStateException {
- throw new UnsupportedOperationException();
- }
-
- public void startDocument(String encoding, Boolean standalone) throws IOException,
- IllegalArgumentException, IllegalStateException {
- append("<?xml version='1.0' encoding='utf-8' standalone='"
- + (standalone ? "yes" : "no") + "' ?>\n");
- }
-
- public XmlSerializer startTag(String namespace, String name) throws IOException,
- IllegalArgumentException, IllegalStateException {
- if (mInTag) {
- append(">\n");
- }
- append('<');
- if (namespace != null) {
- append(namespace);
- append(':');
- }
- append(name);
- mInTag = true;
- return this;
- }
-
- public XmlSerializer text(char[] buf, int start, int len) throws IOException,
- IllegalArgumentException, IllegalStateException {
- if (mInTag) {
- append(">");
- mInTag = false;
- }
- escapeAndAppendString(buf, start, len);
- return this;
- }
-
- public XmlSerializer text(String text) throws IOException, IllegalArgumentException,
- IllegalStateException {
- if (mInTag) {
- append(">");
- mInTag = false;
- }
- escapeAndAppendString(text);
- return this;
- }
-
-}
diff --git a/core/java/com/android/internal/util/HanziToPinyin.java b/core/java/com/android/internal/util/HanziToPinyin.java
new file mode 100644
index 0000000..4368e98
--- /dev/null
+++ b/core/java/com/android/internal/util/HanziToPinyin.java
@@ -0,0 +1,455 @@
+/*
+ * 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.internal.util;
+
+import com.google.android.util.AbstractMessageParser.Token;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * An object to convert Chinese character to its corresponding pinyin string.
+ * For characters with multiple possible pinyin string, only one is selected
+ * according to collator. Polyphone is not supported in this implementation.
+ * This class is implemented to achieve the best runtime performance and minimum
+ * runtime resources with tolerable sacrifice of accuracy. This implementation
+ * highly depends on zh_CN ICU collation data and must be always synchronized with
+ * ICU.
+ */
+public class HanziToPinyin {
+ private static final String TAG = "HanziToPinyin";
+
+ private static final char[] UNIHANS = {
+ '\u5416', '\u54ce', '\u5b89', '\u80ae', '\u51f9', '\u516b', '\u63b0', '\u6273',
+ '\u90a6', '\u52f9', '\u9642', '\u5954', '\u4f3b', '\u7680', '\u782d', '\u706c',
+ '\u618b', '\u6c43', '\u51ab', '\u7676', '\u5cec', '\u5693', '\u5072', '\u53c2',
+ '\u4ed3', '\u64a1', '\u518a', '\u5d7e', '\u564c', '\u6260', '\u62c6', '\u8fbf',
+ '\u4f25', '\u6284', '\u8f66', '\u62bb', '\u9637', '\u5403', '\u5145', '\u62bd',
+ '\u51fa', '\u640b', '\u5ddb', '\u5205', '\u5439', '\u65fe', '\u8e14', '\u5472',
+ '\u4ece', '\u51d1', '\u7c97', '\u6c46', '\u5d14', '\u90a8', '\u6413', '\u5491',
+ '\u5446', '\u4e39', '\u5f53', '\u5200', '\u6074', '\u6265', '\u706f', '\u4efe',
+ '\u55f2', '\u6541', '\u5201', '\u7239', '\u4e01', '\u4e1f', '\u4e1c', '\u543a',
+ '\u5262', '\u8011', '\u5796', '\u5428', '\u591a', '\u59b8', '\u5940', '\u97a5',
+ '\u800c', '\u53d1', '\u5e06', '\u531a', '\u98de', '\u5206', '\u4e30', '\u8985',
+ '\u4ecf', '\u57ba', '\u7d11', '\u592b', '\u7324', '\u65ee', '\u4f85', '\u5e72',
+ '\u5188', '\u768b', '\u6208', '\u7ed9', '\u6839', '\u63ef', '\u55bc', '\u55f0',
+ '\u5de5', '\u52fe', '\u4f30', '\u9e39', '\u4e56', '\u5173', '\u5149', '\u5f52',
+ '\u4e28', '\u8b34', '\u5459', '\u598e', '\u548d', '\u4f44', '\u592f', '\u8320',
+ '\u8bc3', '\u9ed2', '\u62eb', '\u4ea8', '\u53ff', '\u9f41', '\u4e4e', '\u82b1',
+ '\u6000', '\u6b22', '\u5ddf', '\u7070', '\u660f', '\u5419', '\u4e0c', '\u52a0',
+ '\u620b', '\u6c5f', '\u827d', '\u9636', '\u5dfe', '\u5755', '\u5182', '\u4e29',
+ '\u51e5', '\u59e2', '\u5658', '\u519b', '\u5494', '\u5f00', '\u938e', '\u5ffc',
+ '\u5c3b', '\u533c', '\u808e', '\u52a5', '\u7a7a', '\u62a0', '\u625d', '\u5938',
+ '\u84af', '\u5bbd', '\u5321', '\u4e8f', '\u5764', '\u6269', '\u62c9', '\u4f86',
+ '\u5170', '\u5577', '\u635e', '\u4ec2', '\u96f7', '\u8137', '\u68f1', '\u695e',
+ '\u550e', '\u4fe9', '\u5afe', '\u826f', '\u8e7d', '\u57d3', '\u53b8', '\u62ce',
+ '\u6e9c', '\u9f99', '\u5a04', '\u565c', '\u5b6a', '\u62a1', '\u9831', '\u5988',
+ '\u57cb', '\u989f', '\u7264', '\u732b', '\u5445', '\u95e8', '\u6c13', '\u54aa',
+ '\u5b80', '\u55b5', '\u4e5c', '\u6c11', '\u540d', '\u8c2c', '\u6478', '\u725f',
+ '\u6bcd', '\u62cf', '\u8149', '\u56e1', '\u56d4', '\u5b6c', '\u8bb7', '\u5a1e',
+ '\u5ae9', '\u80fd', '\u92b0', '\u62c8', '\u5a18', '\u9e1f', '\u634f', '\u56dc',
+ '\u5b81', '\u599e', '\u519c', '\u7fba', '\u5974', '\u597b', '\u9ec1', '\u90cd',
+ '\u5662', '\u8bb4', '\u5991', '\u62cd', '\u7705', '\u6c78', '\u629b', '\u5478',
+ '\u55b7', '\u5309', '\u4e76', '\u7247', '\u527d', '\u6c15', '\u59d8', '\u4e52',
+ '\u948b', '\u5256', '\u4ec6', '\u4e03', '\u6390', '\u5343', '\u545b', '\u6084',
+ '\u5207', '\u4eb2', '\u9751', '\u5b86', '\u74d7', '\u533a', '\u5cd1', '\u7094',
+ '\u590b', '\u5465', '\u7a63', '\u835b', '\u60f9', '\u4eba', '\u6254', '\u65e5',
+ '\u620e', '\u53b9', '\u909a', '\u5827', '\u6875', '\u95f0', '\u633c', '\u4ee8',
+ '\u6be2', '\u4e09', '\u6852', '\u63bb', '\u8272', '\u68ee', '\u50e7', '\u6740',
+ '\u7b5b', '\u5c71', '\u4f24', '\u5f30', '\u5962', '\u7533', '\u5347', '\u5c38',
+ '\u53ce', '\u4e66', '\u5237', '\u8870', '\u95e9', '\u53cc', '\u8c01', '\u542e',
+ '\u8bf4', '\u53b6', '\u5fea', '\u51c1', '\u82cf', '\u72fb', '\u590a', '\u5b59',
+ '\u5506', '\u4ed6', '\u5b61', '\u574d', '\u6c64', '\u5932', '\u5fd1', '\u81af',
+ '\u5254', '\u5929', '\u65eb', '\u6017', '\u5385', '\u70b5', '\u5077', '\u51f8',
+ '\u6e4d', '\u63a8', '\u541e', '\u8bac', '\u52b8', '\u6b6a', '\u5f2f', '\u5c23',
+ '\u5371', '\u6637', '\u7fc1', '\u631d', '\u4e4c', '\u5915', '\u5477', '\u4ed9',
+ '\u4e61', '\u7071', '\u4e9b', '\u5fc3', '\u5174', '\u51f6', '\u4f11', '\u620c',
+ '\u5405', '\u75b6', '\u7025', '\u4e2b', '\u54bd', '\u592e', '\u5e7a', '\u503b',
+ '\u4e00', '\u4e5a', '\u5e94', '\u5537', '\u4f63', '\u4f18', '\u7ea1', '\u56e6',
+ '\u66f0', '\u8480', '\u5e00', '\u707d', '\u5142', '\u7242', '\u50ae', '\u556b',
+ '\u9c61', '\u600e', '\u66fd', '\u5412', '\u635a', '\u6cbe', '\u5f20', '\u4f4b',
+ '\u8707', '\u8d1e', '\u9eee', '\u4e4b', '\u4e2d', '\u5dde', '\u6731', '\u6293',
+ '\u62fd', '\u4e13', '\u5986', '\u96b9', '\u5b92', '\u5353', '\u4ed4', '\u5b97',
+ '\u90b9', '\u79df', '\u5297', '\u55fa', '\u5c0a', '\u6628',
+ };
+ private final static byte[][] PINYINS = {
+ {65, 00, 00, 00, 00, 00, }, {65, 73, 00, 00, 00, 00, },
+ {65, 78, 00, 00, 00, 00, }, {65, 78, 71, 00, 00, 00, },
+ {65, 79, 00, 00, 00, 00, }, {66, 65, 00, 00, 00, 00, },
+ {66, 65, 73, 00, 00, 00, }, {66, 65, 78, 00, 00, 00, },
+ {66, 65, 78, 71, 00, 00, }, {66, 65, 79, 00, 00, 00, },
+ {66, 69, 73, 00, 00, 00, }, {66, 69, 78, 00, 00, 00, },
+ {66, 69, 78, 71, 00, 00, }, {66, 73, 00, 00, 00, 00, },
+ {66, 73, 65, 78, 00, 00, }, {66, 73, 65, 79, 00, 00, },
+ {66, 73, 69, 00, 00, 00, }, {66, 73, 78, 00, 00, 00, },
+ {66, 73, 78, 71, 00, 00, }, {66, 79, 00, 00, 00, 00, },
+ {66, 85, 00, 00, 00, 00, }, {67, 65, 00, 00, 00, 00, },
+ {67, 65, 73, 00, 00, 00, }, {67, 65, 78, 00, 00, 00, },
+ {67, 65, 78, 71, 00, 00, }, {67, 65, 79, 00, 00, 00, },
+ {67, 69, 00, 00, 00, 00, }, {67, 69, 78, 00, 00, 00, },
+ {67, 69, 78, 71, 00, 00, }, {67, 72, 65, 00, 00, 00, },
+ {67, 72, 65, 73, 00, 00, }, {67, 72, 65, 78, 00, 00, },
+ {67, 72, 65, 78, 71, 00, }, {67, 72, 65, 79, 00, 00, },
+ {67, 72, 69, 00, 00, 00, }, {67, 72, 69, 78, 00, 00, },
+ {67, 72, 69, 78, 71, 00, }, {67, 72, 73, 00, 00, 00, },
+ {67, 72, 79, 78, 71, 00, }, {67, 72, 79, 85, 00, 00, },
+ {67, 72, 85, 00, 00, 00, }, {67, 72, 85, 65, 73, 00, },
+ {67, 72, 85, 65, 78, 00, }, {67, 72, 85, 65, 78, 71, },
+ {67, 72, 85, 73, 00, 00, }, {67, 72, 85, 78, 00, 00, },
+ {67, 72, 85, 79, 00, 00, }, {67, 73, 00, 00, 00, 00, },
+ {67, 79, 78, 71, 00, 00, }, {67, 79, 85, 00, 00, 00, },
+ {67, 85, 00, 00, 00, 00, }, {67, 85, 65, 78, 00, 00, },
+ {67, 85, 73, 00, 00, 00, }, {67, 85, 78, 00, 00, 00, },
+ {67, 85, 79, 00, 00, 00, }, {68, 65, 00, 00, 00, 00, },
+ {68, 65, 73, 00, 00, 00, }, {68, 65, 78, 00, 00, 00, },
+ {68, 65, 78, 71, 00, 00, }, {68, 65, 79, 00, 00, 00, },
+ {68, 69, 00, 00, 00, 00, }, {68, 69, 78, 00, 00, 00, },
+ {68, 69, 78, 71, 00, 00, }, {68, 73, 00, 00, 00, 00, },
+ {68, 73, 65, 00, 00, 00, }, {68, 73, 65, 78, 00, 00, },
+ {68, 73, 65, 79, 00, 00, }, {68, 73, 69, 00, 00, 00, },
+ {68, 73, 78, 71, 00, 00, }, {68, 73, 85, 00, 00, 00, },
+ {68, 79, 78, 71, 00, 00, }, {68, 79, 85, 00, 00, 00, },
+ {68, 85, 00, 00, 00, 00, }, {68, 85, 65, 78, 00, 00, },
+ {68, 85, 73, 00, 00, 00, }, {68, 85, 78, 00, 00, 00, },
+ {68, 85, 79, 00, 00, 00, }, {69, 00, 00, 00, 00, 00, },
+ {69, 78, 00, 00, 00, 00, }, {69, 78, 71, 00, 00, 00, },
+ {69, 82, 00, 00, 00, 00, }, {70, 65, 00, 00, 00, 00, },
+ {70, 65, 78, 00, 00, 00, }, {70, 65, 78, 71, 00, 00, },
+ {70, 69, 73, 00, 00, 00, }, {70, 69, 78, 00, 00, 00, },
+ {70, 69, 78, 71, 00, 00, }, {70, 73, 65, 79, 00, 00, },
+ {70, 79, 00, 00, 00, 00, }, {70, 85, 00, 00, 00, 00, },
+ {70, 79, 85, 00, 00, 00, }, {70, 85, 00, 00, 00, 00, },
+ {71, 85, 73, 00, 00, 00, }, {71, 65, 00, 00, 00, 00, },
+ {71, 65, 73, 00, 00, 00, }, {71, 65, 78, 00, 00, 00, },
+ {71, 65, 78, 71, 00, 00, }, {71, 65, 79, 00, 00, 00, },
+ {71, 69, 00, 00, 00, 00, }, {71, 69, 73, 00, 00, 00, },
+ {71, 69, 78, 00, 00, 00, }, {71, 69, 78, 71, 00, 00, },
+ {74, 73, 69, 00, 00, 00, }, {71, 69, 00, 00, 00, 00, },
+ {71, 79, 78, 71, 00, 00, }, {71, 79, 85, 00, 00, 00, },
+ {71, 85, 00, 00, 00, 00, }, {71, 85, 65, 00, 00, 00, },
+ {71, 85, 65, 73, 00, 00, }, {71, 85, 65, 78, 00, 00, },
+ {71, 85, 65, 78, 71, 00, }, {71, 85, 73, 00, 00, 00, },
+ {71, 85, 78, 00, 00, 00, }, {71, 85, 65, 78, 00, 00, },
+ {71, 85, 79, 00, 00, 00, }, {72, 65, 00, 00, 00, 00, },
+ {72, 65, 73, 00, 00, 00, }, {72, 65, 78, 00, 00, 00, },
+ {72, 65, 78, 71, 00, 00, }, {72, 65, 79, 00, 00, 00, },
+ {72, 69, 00, 00, 00, 00, }, {72, 69, 73, 00, 00, 00, },
+ {72, 69, 78, 00, 00, 00, }, {72, 69, 78, 71, 00, 00, },
+ {72, 79, 78, 71, 00, 00, }, {72, 79, 85, 00, 00, 00, },
+ {72, 85, 00, 00, 00, 00, }, {72, 85, 65, 00, 00, 00, },
+ {72, 85, 65, 73, 00, 00, }, {72, 85, 65, 78, 00, 00, },
+ {72, 85, 65, 78, 71, 00, }, {72, 85, 73, 00, 00, 00, },
+ {72, 85, 78, 00, 00, 00, }, {72, 85, 79, 00, 00, 00, },
+ {74, 73, 00, 00, 00, 00, }, {74, 73, 65, 00, 00, 00, },
+ {74, 73, 65, 78, 00, 00, }, {74, 73, 65, 78, 71, 00, },
+ {74, 73, 65, 79, 00, 00, }, {74, 73, 69, 00, 00, 00, },
+ {74, 73, 78, 00, 00, 00, }, {74, 73, 78, 71, 00, 00, },
+ {74, 73, 79, 78, 71, 00, }, {74, 73, 85, 00, 00, 00, },
+ {74, 85, 00, 00, 00, 00, }, {74, 85, 65, 78, 00, 00, },
+ {74, 85, 69, 00, 00, 00, }, {74, 85, 78, 00, 00, 00, },
+ {75, 65, 00, 00, 00, 00, }, {75, 65, 73, 00, 00, 00, },
+ {75, 65, 78, 00, 00, 00, }, {75, 65, 78, 71, 00, 00, },
+ {75, 65, 79, 00, 00, 00, }, {75, 69, 00, 00, 00, 00, },
+ {75, 69, 78, 00, 00, 00, }, {75, 69, 78, 71, 00, 00, },
+ {75, 79, 78, 71, 00, 00, }, {75, 79, 85, 00, 00, 00, },
+ {75, 85, 00, 00, 00, 00, }, {75, 85, 65, 00, 00, 00, },
+ {75, 85, 65, 73, 00, 00, }, {75, 85, 65, 78, 00, 00, },
+ {75, 85, 65, 78, 71, 00, }, {75, 85, 73, 00, 00, 00, },
+ {75, 85, 78, 00, 00, 00, }, {75, 85, 79, 00, 00, 00, },
+ {76, 65, 00, 00, 00, 00, }, {76, 65, 73, 00, 00, 00, },
+ {76, 65, 78, 00, 00, 00, }, {76, 65, 78, 71, 00, 00, },
+ {76, 65, 79, 00, 00, 00, }, {76, 69, 00, 00, 00, 00, },
+ {76, 69, 73, 00, 00, 00, }, {76, 73, 00, 00, 00, 00, },
+ {76, 73, 78, 71, 00, 00, }, {76, 69, 78, 71, 00, 00, },
+ {76, 73, 00, 00, 00, 00, }, {76, 73, 65, 00, 00, 00, },
+ {76, 73, 65, 78, 00, 00, }, {76, 73, 65, 78, 71, 00, },
+ {76, 73, 65, 79, 00, 00, }, {76, 73, 69, 00, 00, 00, },
+ {76, 73, 78, 00, 00, 00, }, {76, 73, 78, 71, 00, 00, },
+ {76, 73, 85, 00, 00, 00, }, {76, 79, 78, 71, 00, 00, },
+ {76, 79, 85, 00, 00, 00, }, {76, 85, 00, 00, 00, 00, },
+ {76, 85, 65, 78, 00, 00, }, {76, 85, 78, 00, 00, 00, },
+ {76, 85, 79, 00, 00, 00, }, {77, 65, 00, 00, 00, 00, },
+ {77, 65, 73, 00, 00, 00, }, {77, 65, 78, 00, 00, 00, },
+ {77, 65, 78, 71, 00, 00, }, {77, 65, 79, 00, 00, 00, },
+ {77, 69, 73, 00, 00, 00, }, {77, 69, 78, 00, 00, 00, },
+ {77, 69, 78, 71, 00, 00, }, {77, 73, 00, 00, 00, 00, },
+ {77, 73, 65, 78, 00, 00, }, {77, 73, 65, 79, 00, 00, },
+ {77, 73, 69, 00, 00, 00, }, {77, 73, 78, 00, 00, 00, },
+ {77, 73, 78, 71, 00, 00, }, {77, 73, 85, 00, 00, 00, },
+ {77, 79, 00, 00, 00, 00, }, {77, 79, 85, 00, 00, 00, },
+ {77, 85, 00, 00, 00, 00, }, {78, 65, 00, 00, 00, 00, },
+ {78, 65, 73, 00, 00, 00, }, {78, 65, 78, 00, 00, 00, },
+ {78, 65, 78, 71, 00, 00, }, {78, 65, 79, 00, 00, 00, },
+ {78, 69, 00, 00, 00, 00, }, {78, 69, 73, 00, 00, 00, },
+ {78, 69, 78, 00, 00, 00, }, {78, 69, 78, 71, 00, 00, },
+ {78, 73, 00, 00, 00, 00, }, {78, 73, 65, 78, 00, 00, },
+ {78, 73, 65, 78, 71, 00, }, {78, 73, 65, 79, 00, 00, },
+ {78, 73, 69, 00, 00, 00, }, {78, 73, 78, 00, 00, 00, },
+ {78, 73, 78, 71, 00, 00, }, {78, 73, 85, 00, 00, 00, },
+ {78, 79, 78, 71, 00, 00, }, {78, 79, 85, 00, 00, 00, },
+ {78, 85, 00, 00, 00, 00, }, {78, 85, 65, 78, 00, 00, },
+ {78, 85, 78, 00, 00, 00, }, {78, 85, 79, 00, 00, 00, },
+ {79, 00, 00, 00, 00, 00, }, {79, 85, 00, 00, 00, 00, },
+ {80, 65, 00, 00, 00, 00, }, {80, 65, 73, 00, 00, 00, },
+ {80, 65, 78, 00, 00, 00, }, {80, 65, 78, 71, 00, 00, },
+ {80, 65, 79, 00, 00, 00, }, {80, 69, 73, 00, 00, 00, },
+ {80, 69, 78, 00, 00, 00, }, {80, 69, 78, 71, 00, 00, },
+ {80, 73, 00, 00, 00, 00, }, {80, 73, 65, 78, 00, 00, },
+ {80, 73, 65, 79, 00, 00, }, {80, 73, 69, 00, 00, 00, },
+ {80, 73, 78, 00, 00, 00, }, {80, 73, 78, 71, 00, 00, },
+ {80, 79, 00, 00, 00, 00, }, {80, 79, 85, 00, 00, 00, },
+ {80, 85, 00, 00, 00, 00, }, {81, 73, 00, 00, 00, 00, },
+ {81, 73, 65, 00, 00, 00, }, {81, 73, 65, 78, 00, 00, },
+ {81, 73, 65, 78, 71, 00, }, {81, 73, 65, 79, 00, 00, },
+ {81, 73, 69, 00, 00, 00, }, {81, 73, 78, 00, 00, 00, },
+ {81, 73, 78, 71, 00, 00, }, {81, 73, 79, 78, 71, 00, },
+ {81, 73, 85, 00, 00, 00, }, {81, 85, 00, 00, 00, 00, },
+ {81, 85, 65, 78, 00, 00, }, {81, 85, 69, 00, 00, 00, },
+ {81, 85, 78, 00, 00, 00, }, {82, 65, 78, 00, 00, 00, },
+ {82, 65, 78, 71, 00, 00, }, {82, 65, 79, 00, 00, 00, },
+ {82, 69, 00, 00, 00, 00, }, {82, 69, 78, 00, 00, 00, },
+ {82, 69, 78, 71, 00, 00, }, {82, 73, 00, 00, 00, 00, },
+ {82, 79, 78, 71, 00, 00, }, {82, 79, 85, 00, 00, 00, },
+ {82, 85, 00, 00, 00, 00, }, {82, 85, 65, 78, 00, 00, },
+ {82, 85, 73, 00, 00, 00, }, {82, 85, 78, 00, 00, 00, },
+ {82, 85, 79, 00, 00, 00, }, {83, 65, 00, 00, 00, 00, },
+ {83, 65, 73, 00, 00, 00, }, {83, 65, 78, 00, 00, 00, },
+ {83, 65, 78, 71, 00, 00, }, {83, 65, 79, 00, 00, 00, },
+ {83, 69, 00, 00, 00, 00, }, {83, 69, 78, 00, 00, 00, },
+ {83, 69, 78, 71, 00, 00, }, {83, 72, 65, 00, 00, 00, },
+ {83, 72, 65, 73, 00, 00, }, {83, 72, 65, 78, 00, 00, },
+ {83, 72, 65, 78, 71, 00, }, {83, 72, 65, 79, 00, 00, },
+ {83, 72, 69, 00, 00, 00, }, {83, 72, 69, 78, 00, 00, },
+ {83, 72, 69, 78, 71, 00, }, {83, 72, 73, 00, 00, 00, },
+ {83, 72, 79, 85, 00, 00, }, {83, 72, 85, 00, 00, 00, },
+ {83, 72, 85, 65, 00, 00, }, {83, 72, 85, 65, 73, 00, },
+ {83, 72, 85, 65, 78, 00, }, {83, 72, 85, 65, 78, 71, },
+ {83, 72, 85, 73, 00, 00, }, {83, 72, 85, 78, 00, 00, },
+ {83, 72, 85, 79, 00, 00, }, {83, 73, 00, 00, 00, 00, },
+ {83, 79, 78, 71, 00, 00, }, {83, 79, 85, 00, 00, 00, },
+ {83, 85, 00, 00, 00, 00, }, {83, 85, 65, 78, 00, 00, },
+ {83, 85, 73, 00, 00, 00, }, {83, 85, 78, 00, 00, 00, },
+ {83, 85, 79, 00, 00, 00, }, {84, 65, 00, 00, 00, 00, },
+ {84, 65, 73, 00, 00, 00, }, {84, 65, 78, 00, 00, 00, },
+ {84, 65, 78, 71, 00, 00, }, {84, 65, 79, 00, 00, 00, },
+ {84, 69, 00, 00, 00, 00, }, {84, 69, 78, 71, 00, 00, },
+ {84, 73, 00, 00, 00, 00, }, {84, 73, 65, 78, 00, 00, },
+ {84, 73, 65, 79, 00, 00, }, {84, 73, 69, 00, 00, 00, },
+ {84, 73, 78, 71, 00, 00, }, {84, 79, 78, 71, 00, 00, },
+ {84, 79, 85, 00, 00, 00, }, {84, 85, 00, 00, 00, 00, },
+ {84, 85, 65, 78, 00, 00, }, {84, 85, 73, 00, 00, 00, },
+ {84, 85, 78, 00, 00, 00, }, {84, 85, 79, 00, 00, 00, },
+ {87, 65, 00, 00, 00, 00, }, {87, 65, 73, 00, 00, 00, },
+ {87, 65, 78, 00, 00, 00, }, {87, 65, 78, 71, 00, 00, },
+ {87, 69, 73, 00, 00, 00, }, {87, 69, 78, 00, 00, 00, },
+ {87, 69, 78, 71, 00, 00, }, {87, 79, 00, 00, 00, 00, },
+ {87, 85, 00, 00, 00, 00, }, {88, 73, 00, 00, 00, 00, },
+ {88, 73, 65, 00, 00, 00, }, {88, 73, 65, 78, 00, 00, },
+ {88, 73, 65, 78, 71, 00, }, {88, 73, 65, 79, 00, 00, },
+ {88, 73, 69, 00, 00, 00, }, {88, 73, 78, 00, 00, 00, },
+ {88, 73, 78, 71, 00, 00, }, {88, 73, 79, 78, 71, 00, },
+ {88, 73, 85, 00, 00, 00, }, {88, 85, 00, 00, 00, 00, },
+ {88, 85, 65, 78, 00, 00, }, {88, 85, 69, 00, 00, 00, },
+ {88, 85, 78, 00, 00, 00, }, {89, 65, 00, 00, 00, 00, },
+ {89, 65, 78, 00, 00, 00, }, {89, 65, 78, 71, 00, 00, },
+ {89, 65, 79, 00, 00, 00, }, {89, 69, 00, 00, 00, 00, },
+ {89, 73, 00, 00, 00, 00, }, {89, 73, 78, 00, 00, 00, },
+ {89, 73, 78, 71, 00, 00, }, {89, 79, 00, 00, 00, 00, },
+ {89, 79, 78, 71, 00, 00, }, {89, 79, 85, 00, 00, 00, },
+ {89, 85, 00, 00, 00, 00, }, {89, 85, 65, 78, 00, 00, },
+ {89, 85, 69, 00, 00, 00, }, {89, 85, 78, 00, 00, 00, },
+ {90, 65, 00, 00, 00, 00, }, {90, 65, 73, 00, 00, 00, },
+ {90, 65, 78, 00, 00, 00, }, {90, 65, 78, 71, 00, 00, },
+ {90, 65, 79, 00, 00, 00, }, {90, 69, 00, 00, 00, 00, },
+ {90, 69, 73, 00, 00, 00, }, {90, 69, 78, 00, 00, 00, },
+ {90, 69, 78, 71, 00, 00, }, {90, 72, 65, 00, 00, 00, },
+ {90, 72, 65, 73, 00, 00, }, {90, 72, 65, 78, 00, 00, },
+ {90, 72, 65, 78, 71, 00, }, {90, 72, 65, 79, 00, 00, },
+ {90, 72, 69, 00, 00, 00, }, {90, 72, 69, 78, 00, 00, },
+ {90, 72, 69, 78, 71, 00, }, {90, 72, 73, 00, 00, 00, },
+ {90, 72, 79, 78, 71, 00, }, {90, 72, 79, 85, 00, 00, },
+ {90, 72, 85, 00, 00, 00, }, {90, 72, 85, 65, 00, 00, },
+ {90, 72, 85, 65, 73, 00, }, {90, 72, 85, 65, 78, 00, },
+ {90, 72, 85, 65, 78, 71, }, {90, 72, 85, 73, 00, 00, },
+ {90, 72, 85, 78, 00, 00, }, {90, 72, 85, 79, 00, 00, },
+ {90, 73, 00, 00, 00, 00, }, {90, 79, 78, 71, 00, 00, },
+ {90, 79, 85, 00, 00, 00, }, {90, 85, 00, 00, 00, 00, },
+ {90, 85, 65, 78, 00, 00, }, {90, 85, 73, 00, 00, 00, },
+ {90, 85, 78, 00, 00, 00, }, {90, 85, 79, 00, 00, 00, },
+
+ };
+
+ /** First and last Chinese character with known Pinyin according to zh collation */
+ private static final String FIRST_UNIHAN = "\u5416";
+ private static final String LAST_UNIHAN = "\u5497";
+ private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);
+
+ private static HanziToPinyin sInstance;
+ private final boolean mHasChinaCollator;
+
+ public static class Token {
+ /**
+ * Separator between target string for each source char
+ */
+ public static final String SEPARATOR = " ";
+
+ public static final int ASCII = 1;
+ public static final int PINYIN = 2;
+ public static final int UNKNOWN = 3;
+
+ /**
+ * Type of this token, ASCII, PINYIN or UNKNOWN.
+ */
+ public int type;
+ /**
+ * Original string before translation.
+ */
+ public String source;
+ /**
+ * Translated string of source. For Han, target is corresponding Pinyin.
+ * Otherwise target is original string in source.
+ */
+ public String target;
+ }
+
+ protected HanziToPinyin(boolean hasChinaCollator) {
+ mHasChinaCollator = hasChinaCollator;
+ }
+
+ public static HanziToPinyin getInstance() {
+ synchronized(HanziToPinyin.class) {
+ if (sInstance != null) {
+ return sInstance;
+ }
+ // Check if zh_CN collation data is available
+ final Locale locale[] = Collator.getAvailableLocales();
+ for (int i = 0; i < locale.length; i++) {
+ if (locale[i].equals(Locale.CHINA)) {
+ sInstance = new HanziToPinyin(true);
+ return sInstance;
+ }
+ }
+ sInstance = new HanziToPinyin(false);
+ return sInstance;
+ }
+ }
+
+ private Token getToken(char character) {
+ Token token = new Token();
+ final String letter = Character.toString(character);
+ token.source = letter;
+ int offset = -1;
+ int cmp;
+ if (character < 256) {
+ token.type = Token.ASCII;
+ token.target = letter;
+ return token;
+ } else {
+ cmp = COLLATOR.compare(letter, FIRST_UNIHAN);
+ if (cmp < 0) {
+ token.type = Token.UNKNOWN;
+ token.target = letter;
+ return token;
+ } else if (cmp == 0) {
+ token.type = Token.PINYIN;
+ offset = 0;
+ } else {
+ cmp = COLLATOR.compare(letter, LAST_UNIHAN);
+ if (cmp > 0) {
+ token.type = Token.UNKNOWN;
+ token.target = letter;
+ return token;
+ } else if (cmp == 0) {
+ token.type = Token.PINYIN;
+ offset = UNIHANS.length - 1;
+ }
+ }
+ }
+
+ token.type = Token.PINYIN;
+ if (offset < 0) {
+ int begin = 0;
+ int end = UNIHANS.length - 1;
+ while (begin <= end) {
+ offset = (begin + end) / 2;
+ final String unihan = Character.toString(UNIHANS[offset]);
+ cmp = COLLATOR.compare(letter, unihan);
+ if (cmp == 0) {
+ break;
+ } else if (cmp > 0) {
+ begin = offset + 1;
+ } else {
+ end = offset - 1;
+ }
+ }
+ }
+ if (cmp < 0) {
+ offset--;
+ }
+ StringBuilder pinyin = new StringBuilder();
+ for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) {
+ pinyin.append((char)PINYINS[offset][j]);
+ }
+ token.target = pinyin.toString();
+ return token;
+ }
+
+ public ArrayList<Token> get(final String input) {
+ if (!mHasChinaCollator || TextUtils.isEmpty(input)) {
+ return null;
+ }
+
+ ArrayList<Token> tokens = new ArrayList<Token>();
+ Token currentToken;
+
+ final int inputLength = input.length();
+
+ currentToken = getToken(input.charAt(0));
+
+ for (int i = 1; i < inputLength; i++) {
+ final char character = input.charAt(i);
+ Token token = getToken(character);
+
+ if (token.type != currentToken.type) {
+ currentToken.target = currentToken.target.trim();
+ tokens.add(currentToken);
+ currentToken = token;
+ } else {
+ switch (token.type) {
+ case Token.ASCII:
+ case Token.UNKNOWN:
+ currentToken.source += token.source;
+ currentToken.target += token.target;
+ break;
+ case Token.PINYIN:
+ currentToken.source += token.source;
+ currentToken.target += " " + token.target;
+ break;
+ }
+ }
+ }
+
+ currentToken.target = currentToken.target.trim();
+ tokens.add(currentToken);
+
+ return tokens;
+ }
+}
diff --git a/core/java/com/android/internal/util/HierarchicalState.java b/core/java/com/android/internal/util/HierarchicalState.java
new file mode 100644
index 0000000..002338a
--- /dev/null
+++ b/core/java/com/android/internal/util/HierarchicalState.java
@@ -0,0 +1,75 @@
+/**
+ * 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.internal.util;
+
+import android.os.Message;
+
+/**
+ * {@hide}
+ *
+ * The abstract class for implementing states in a
+ * HierarchicalStateMachine and HandlerStateMachine.
+ */
+public abstract class HierarchicalState {
+
+ /**
+ * Constructor
+ */
+ protected HierarchicalState() {
+ }
+
+ /**
+ * Called when a state is entered.
+ */
+ protected void enter() {
+ }
+
+ /**
+ * Called when a message is to be processed by the
+ * state machine.
+ *
+ * This routine is never reentered thus no synchronization
+ * is needed as only one processMessage method will ever be
+ * executing within a state machine at any given time. This
+ * does mean that processing by this routine must be completed
+ * as expeditiously as possible as no subsequent messages will
+ * be processed until this routine returns.
+ *
+ * @param msg to process
+ * @return true if processing has completed and false
+ * if the parent state's processMessage should
+ * be invoked.
+ */
+ abstract protected boolean processMessage(Message msg);
+
+ /**
+ * Called when a state is exited.
+ */
+ protected void exit() {
+ }
+
+ /**
+ * @return name of state, but default returns the states
+ * class name. An instance name would be better but requiring
+ * it seems unnecessary.
+ */
+ public String getName() {
+ String name = getClass().getName();
+ int lastDollar = name.lastIndexOf('$');
+ return name.substring(lastDollar + 1);
+ }
+}
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
new file mode 100644
index 0000000..a1c5078
--- /dev/null
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -0,0 +1,1164 @@
+/**
+ * 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.internal.util;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * {@hide}
+ *
+ * A hierarchical state machine is a state machine which processes messages
+ * and can have states arranged hierarchically. A state is a <code>HierarchicalState</code>
+ * object and must implement <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
+ * The enter/exit methods are equivalent to the construction and destruction
+ * in Object Oriented programming and are used to perform initialization and
+ * cleanup of the state respectively. The <code>getName</code> method returns the
+ * name of the state the default implementation returns the class name it may be
+ * desirable to have this return the name of the state instance name instead.
+ * In particular if a particular state class has multiple instances.
+ *
+ * When a state machine is created <code>addState</code> is used to build the
+ * hierarchy and <code>setInitialState</code> is used to identify which of these
+ * is the initial state. After construction the programmer calls <code>start</code>
+ * which initializes the state machine and calls <code>enter</code> for all of the initial
+ * state's hierarchy, starting at its eldest parent. For example given the simple
+ * state machine below after start is called mP1.enter will have been called and
+ * then mS1.enter.
+<code>
+ mP1
+ / \
+ mS2 mS1 ----> initial state
+</code>
+ * After the state machine is created and started, messages are sent to a state
+ * machine using <code>sendMessage</code and the messages are created using
+ * <code>obtainMessage</code>. When the state machine receives a message the
+ * current state's <code>processMessage</code> is invoked. In the above example
+ * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code>
+ * to change the current state to a new state
+ *
+ * Each state in the state machine may have a zero or one parent states and if
+ * a child state is unable to handle a message it may have the message processed
+ * by its parent by returning false. If a message is never processed <code>unhandledMessage</code>
+ * will be invoked to give one last chance for the state machine to process
+ * the message.
+ *
+ * When all processing is completed a state machine may choose to call
+ * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code>
+ * returns the state machine will transfer to an internal <code>HaltingState</code>
+ * and invoke <code>halting</code>. Any message subsequently received by the state
+ * machine will cause <code>haltedProcessMessage</code> to be invoked.
+ *
+ * In addition to <code>processMessage</code> each <code>HierarchicalState</code> has
+ * an <code>enter</code> method and <code>exit</exit> method which may be overridden.
+ *
+ * Since the states are arranged in a hierarchy transitioning to a new state
+ * causes current states to be exited and new states to be entered. To determine
+ * the list of states to be entered/exited the common parent closest to
+ * the current state is found. We then exit from the current state and its
+ * parent's up to but not including the common parent state and then enter all
+ * of the new states below the common parent down to the destination state.
+ * If there is no common parent all states are exited and then the new states
+ * are entered.
+ *
+ * Two other methods that states can use are <code>deferMessage</code> and
+ * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends
+ * a message but places it on the front of the queue rather than the back. The
+ * <code>deferMessage</code> causes the message to be saved on a list until a
+ * transition is made to a new state. At which time all of the deferred messages
+ * will be put on the front of the state machine queue with the oldest message
+ * at the front. These will then be processed by the new current state before
+ * any other messages that are on the queue or might be added later. Both of
+ * these are protected and may only be invoked from within a state machine.
+ *
+ * To illustrate some of these properties we'll use state machine with 8
+ * state hierarchy:
+<code>
+ mP0
+ / \
+ mP1 mS0
+ / \
+ mS2 mS1
+ / \ \
+ mS3 mS4 mS5 ---> initial state
+</code>
+ *
+ * After starting mS5 the list of active states is mP0, mP1, mS1 and mS5.
+ * So the order of calling processMessage when a message is received is mS5,
+ * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
+ * message by returning false.
+ *
+ * Now assume mS5.processMessage receives a message it can handle, and during
+ * the handling determines the machine should changes states. It would call
+ * transitionTo(mS4) and return true. Immediately after returning from
+ * processMessage the state machine runtime will find the common parent,
+ * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then
+ * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
+ * when the next message is received mS4.processMessage will be invoked.
+ *
+ * To assist in describing an HSM a simple grammar has been created which
+ * is informally defined here and a formal EBNF description is at the end
+ * of the class comment.
+ *
+ * An HSM starts with the name and includes a set of hierarchical states.
+ * A state is preceeded by one or more plus signs (+), to indicate its
+ * depth and a hash (#) if its the initial state. Child states follow their
+ * parents and have one more plus sign then their parent. Inside a state
+ * are a series of messages, the actions they perform and if the processing
+ * is complete ends with a period (.). If processing isn't complete and
+ * the parent should process the message it ends with a caret (^). The
+ * actions include send a message ($MESSAGE), defer a message (%MESSAGE),
+ * transition to a new state (>MESSAGE) and an if statement
+ * (if ( expression ) { list of actions }.)
+ *
+ * The Hsm HelloWorld could documented as:
+ *
+ * HelloWorld {
+ * + # mState1.
+ * }
+ *
+ * and interpreted as HSM HelloWorld:
+ *
+ * mState1 a root state (single +) and initial state (#) which
+ * processes all messages completely, the period (.).
+ *
+ * The implementation is:
+<code>
+class HelloWorld extends HierarchicalStateMachine {
+ Hsm1(String name) {
+ super(name);
+ addState(mState1);
+ setInitialState(mState1);
+ }
+
+ public static HelloWorld makeHelloWorld() {
+ HelloWorld hw = new HelloWorld("hw");
+ hw.start();
+ return hw;
+ }
+
+ class State1 extends HierarchicalState {
+ @Override public boolean processMessage(Message message) {
+ Log.d(TAG, "Hello World");
+ return true;
+ }
+ }
+ State1 mState1 = new State1();
+}
+
+void testHelloWorld() {
+ HelloWorld hw = makeHelloWorld();
+ hw.sendMessage(hw.obtainMessage());
+}
+</code>
+ *
+ * A more interesting state machine is one of four states
+ * with two independent parent states.
+<code>
+ mP1 mP2
+ / \
+ mS2 mS1
+</code>
+ *
+ * documented as:
+ *
+ * Hsm1 {
+ * + mP1 {
+ * CMD_2 {
+ * $CMD_3
+ * %CMD_2
+ * >mS2
+ * }.
+ * }
+ * ++ # mS1 { CMD_1{ >mS1 }^ }
+ * ++ mS2 {
+ * CMD_2{$CMD_4}.
+ * CMD_3{%CMD_3 ; >mP2}.
+ * }
+ *
+ * + mP2 e($CMD_5) {
+ * CMD_3, CMD_4.
+ * CMD_5{>HALT}.
+ * }
+ * }
+ *
+ * and interpreted as HierarchicalStateMachine Hsm1:
+ *
+ * mP1 a root state.
+ * processes message CMD_2 which sends CMD_3, defers CMD_2, and transitions to mS2
+ *
+ * mS1 a child of mP1 is the initial state:
+ * processes message CMD_1 which transitions to itself and returns false to let mP1 handle it.
+ *
+ * mS2 a child of mP1:
+ * processes message CMD_2 which send CMD_4
+ * processes message CMD_3 which defers CMD_3 and transitions to mP2
+ *
+ * mP2 a root state.
+ * on enter it sends CMD_5
+ * processes message CMD_3
+ * processes message CMD_4
+ * processes message CMD_5 which transitions to halt state
+ *
+ * The implementation is below and also in HierarchicalStateMachineTest:
+<code>
+class Hsm1 extends HierarchicalStateMachine {
+ private static final String TAG = "hsm1";
+
+ public static final int CMD_1 = 1;
+ public static final int CMD_2 = 2;
+ public static final int CMD_3 = 3;
+ public static final int CMD_4 = 4;
+ public static final int CMD_5 = 5;
+
+ public static Hsm1 makeHsm1() {
+ Log.d(TAG, "makeHsm1 E");
+ Hsm1 sm = new Hsm1("hsm1");
+ sm.start();
+ Log.d(TAG, "makeHsm1 X");
+ return sm;
+ }
+
+ Hsm1(String name) {
+ super(name);
+ Log.d(TAG, "ctor E");
+
+ // Add states, use indentation to show hierarchy
+ addState(mP1);
+ addState(mS1, mP1);
+ addState(mS2, mP1);
+ addState(mP2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ Log.d(TAG, "ctor X");
+ }
+
+ class P1 extends HierarchicalState {
+ @Override public void enter() {
+ Log.d(TAG, "mP1.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ boolean retVal;
+ Log.d(TAG, "mP1.processMessage what=" + message.what);
+ switch(message.what) {
+ case CMD_2:
+ // CMD_2 will arrive in mS2 before CMD_3
+ sendMessage(obtainMessage(CMD_3));
+ deferMessage(message);
+ transitionTo(mS2);
+ retVal = true;
+ break;
+ default:
+ // Any message we don't understand in this state invokes unhandledMessage
+ retVal = false;
+ break;
+ }
+ return retVal;
+ }
+ @Override public void exit() {
+ Log.d(TAG, "mP1.exit");
+ }
+ }
+
+ class S1 extends HierarchicalState {
+ @Override public void enter() {
+ Log.d(TAG, "mS1.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ Log.d(TAG, "S1.processMessage what=" + message.what);
+ if (message.what == CMD_1) {
+ // Transition to ourself to show that enter/exit is called
+ transitionTo(mS1);
+ return true;
+ } else {
+ // Let parent process all other messages
+ return false;
+ }
+ }
+ @Override public void exit() {
+ Log.d(TAG, "mS1.exit");
+ }
+ }
+
+ class S2 extends HierarchicalState {
+ @Override public void enter() {
+ Log.d(TAG, "mS2.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ boolean retVal;
+ Log.d(TAG, "mS2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_2):
+ sendMessage(obtainMessage(CMD_4));
+ retVal = true;
+ break;
+ case(CMD_3):
+ deferMessage(message);
+ transitionTo(mP2);
+ retVal = true;
+ break;
+ default:
+ retVal = false;
+ break;
+ }
+ return retVal;
+ }
+ @Override public void exit() {
+ Log.d(TAG, "mS2.exit");
+ }
+ }
+
+ class P2 extends HierarchicalState {
+ @Override public void enter() {
+ Log.d(TAG, "mP2.enter");
+ sendMessage(obtainMessage(CMD_5));
+ }
+ @Override public boolean processMessage(Message message) {
+ Log.d(TAG, "P2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_3):
+ break;
+ case(CMD_4):
+ break;
+ case(CMD_5):
+ transitionToHaltingState();
+ break;
+ }
+ return true;
+ }
+ @Override public void exit() {
+ Log.d(TAG, "mP2.exit");
+ }
+ }
+
+ @Override
+ protected void halting() {
+ Log.d(TAG, "halting");
+ synchronized (this) {
+ this.notifyAll();
+ }
+ }
+
+ P1 mP1 = new P1();
+ S1 mS1 = new S1();
+ S2 mS2 = new S2();
+ P2 mP2 = new P2();
+}
+</code>
+ *
+ * If this is executed by sending two messages CMD_1 and CMD_2
+ * (Note the synchronize is only needed because we use hsm.wait())
+ *
+ * Hsm1 hsm = makeHsm1();
+ * synchronize(hsm) {
+ * hsm.sendMessage(obtainMessage(hsm.CMD_1));
+ * hsm.sendMessage(obtainMessage(hsm.CMD_2));
+ * try {
+ * // wait for the messages to be handled
+ * hsm.wait();
+ * } catch (InterruptedException e) {
+ * Log.e(TAG, "exception while waiting " + e.getMessage());
+ * }
+ * }
+ *
+ *
+ * The output is:
+ *
+ * D/hsm1 ( 1999): makeHsm1 E
+ * D/hsm1 ( 1999): ctor E
+ * D/hsm1 ( 1999): ctor X
+ * D/hsm1 ( 1999): mP1.enter
+ * D/hsm1 ( 1999): mS1.enter
+ * D/hsm1 ( 1999): makeHsm1 X
+ * D/hsm1 ( 1999): mS1.processMessage what=1
+ * D/hsm1 ( 1999): mS1.exit
+ * D/hsm1 ( 1999): mS1.enter
+ * D/hsm1 ( 1999): mS1.processMessage what=2
+ * D/hsm1 ( 1999): mP1.processMessage what=2
+ * D/hsm1 ( 1999): mS1.exit
+ * D/hsm1 ( 1999): mS2.enter
+ * D/hsm1 ( 1999): mS2.processMessage what=2
+ * D/hsm1 ( 1999): mS2.processMessage what=3
+ * D/hsm1 ( 1999): mS2.exit
+ * D/hsm1 ( 1999): mP1.exit
+ * D/hsm1 ( 1999): mP2.enter
+ * D/hsm1 ( 1999): mP2.processMessage what=3
+ * D/hsm1 ( 1999): mP2.processMessage what=4
+ * D/hsm1 ( 1999): mP2.processMessage what=5
+ * D/hsm1 ( 1999): mP2.exit
+ * D/hsm1 ( 1999): halting
+ *
+ * Here is the HSM a BNF grammar, this is a first stab at creating an
+ * HSM description language, suggestions corrections or alternatives
+ * would be much appreciated.
+ *
+ * Legend:
+ * {} ::= zero or more
+ * {}+ ::= one or more
+ * [] ::= zero or one
+ * () ::= define a group with "or" semantics.
+ *
+ * HSM EBNF:
+ * HSM = HSM_NAME "{" { STATE }+ "}" ;
+ * HSM_NAME = alpha_numeric_name ;
+ * STATE = INTRODUCE_STATE [ ENTER ] "{" [ MESSAGES ] "}" [ EXIT ] ;
+ * INTRODUCE_STATE = { STATE_DEPTH }+ [ INITIAL_STATE_INDICATOR ] STATE_NAME ;
+ * STATE_DEPTH = "+" ;
+ * INITIAL_STATE_INDICATOR = "#"
+ * ENTER = "e(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
+ * MESSAGES = { MSG_LIST MESSAGE_ACTIONS } ;
+ * MSG_LIST = { MSG_NAME { "," MSG_NAME } ;
+ * EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
+ * PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ;
+ * SEND_ACTION = "$" MSG_NAME ;
+ * DEFER_ACTION = "%" MSG_NAME ;
+ * TRANSITION_ACTION = ">" STATE_NAME ;
+ * HALT_ACTION = ">" HALT ;
+ * MESSAGE_ACTIONS = { "{" ACTION_LIST "}" } [ PROCESS_COMPLETION ] ;
+ * ACTION_LIST = ACTION { (";" | "\n") ACTION } ;
+ * ACTION = IF_ACTION | SEND_ACTION | DEFER_ACTION | TRANSITION_ACTION | HALT_ACTION ;
+ * IF_ACTION = "if(" boolean_expression ")" "{" ACTION_LIST "}"
+ * PROCESS_IN_PARENT_OR_COMPLETE = "^" ;
+ * PROCESS_COMPLETE = "." ;
+ * STATE_NAME = alpha_numeric_name ;
+ * MSG_NAME = alpha_numeric_name | ALL_OTHER_MESSAGES ;
+ * ALL_OTHER_MESSAGES = "*" ;
+ * EXP = boolean_expression ;
+ *
+ * Idioms:
+ * * { %* }. ::= All other messages will be deferred.
+ */
+public class HierarchicalStateMachine {
+
+ private static final String TAG = "HierarchicalStateMachine";
+ private String mName;
+
+ private static class HsmHandler extends Handler {
+
+ /** The debug flag */
+ private boolean mDbg = false;
+
+ /** A list of messages that this state machine has processed */
+ private ProcessedMessages mProcessedMessages = new ProcessedMessages();
+
+ /** true if construction of the state machine has not been completed */
+ private boolean mIsConstructionCompleted;
+
+ /** Stack used to manage the current hierarchy of states */
+ private StateInfo mStateStack[];
+
+ /** Top of mStateStack */
+ private int mStateStackTopIndex = -1;
+
+ /** A temporary stack used to manage the state stack */
+ private StateInfo mTempStateStack[];
+
+ /** The top of the mTempStateStack */
+ private int mTempStateStackCount;
+
+ /** State used when state machine is halted */
+ private HaltingState mHaltingState = new HaltingState();
+
+ /** Reference to the HierarchicalStateMachine */
+ private HierarchicalStateMachine mHsm;
+
+ /**
+ * Information about a state.
+ * Used to maintain the hierarchy.
+ */
+ private class StateInfo {
+ /** The state */
+ HierarchicalState state;
+
+ /** The parent of this state, null if there is no parent */
+ StateInfo parentStateInfo;
+
+ /** True when the state has been entered and on the stack */
+ boolean active;
+
+ /**
+ * Convert StateInfo to string
+ */
+ @Override
+ public String toString() {
+ return "state=" + state.getName() + ",active=" + active
+ + ",parent=" + ((parentStateInfo == null) ?
+ "null" : parentStateInfo.state.getName());
+ }
+ }
+
+ /** The map of all of the states in the state machine */
+ private HashMap<HierarchicalState, StateInfo> mStateInfo =
+ new HashMap<HierarchicalState, StateInfo>();
+
+ /** The initial state that will process the first message */
+ private HierarchicalState mInitialState;
+
+ /** The destination state when transitionTo has been invoked */
+ private HierarchicalState mDestState;
+
+ /** The list of deferred messages */
+ private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
+
+ /**
+ * State entered when transitionToHaltingState is called.
+ */
+ private class HaltingState extends HierarchicalState {
+ @Override
+ public boolean processMessage(Message msg) {
+ mHsm.haltedProcessMessage(msg);
+ return true;
+ }
+ }
+
+ /**
+ * Handle messages sent to the state machine by calling
+ * the current state's processMessage. It also handles
+ * the enter/exit calls and placing any deferred messages
+ * back onto the queue when transitioning to a new state.
+ */
+ @Override
+ public final void handleMessage(Message msg) {
+ if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what);
+
+ /**
+ * Check that construction was completed
+ */
+ if (!mIsConstructionCompleted) {
+ Log.e(TAG, "The start method not called, ignore msg: " + msg);
+ return;
+ }
+
+ /**
+ * Process the message abiding by the hierarchical semantics.
+ */
+ processMsg(msg);
+
+ /**
+ * If transitionTo has been called, exit and then enter
+ * the appropriate states.
+ */
+ if (mDestState != null) {
+ if (mDbg) Log.d(TAG, "handleMessage: new destination call exit");
+
+ /**
+ * Determine the states to exit and enter and return the
+ * common ancestor state of the enter/exit states. Then
+ * invoke the exit methods then the enter methods.
+ */
+ StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(mDestState);
+ invokeExitMethods(commonStateInfo);
+ int stateStackEnteringIndex = moveTempStateStackToStateStack();
+ invokeEnterMethods(stateStackEnteringIndex);
+
+
+ /**
+ * Since we have transitioned to a new state we need to have
+ * any deferred messages moved to the front of the message queue
+ * so they will be processed before any other messages in the
+ * message queue.
+ */
+ moveDeferredMessageAtFrontOfQueue();
+
+ /**
+ * Call halting() if we've transitioned to the halting
+ * state. All subsequent messages will be processed in
+ * in the halting state which invokes haltedProcessMessage(msg);
+ */
+ if (mDestState == mHaltingState) {
+ mHsm.halting();
+ }
+ mDestState = null;
+ }
+
+ if (mDbg) Log.d(TAG, "handleMessage: X");
+ }
+
+ /**
+ * Complete the construction of the state machine.
+ */
+ private final void completeConstruction() {
+ if (mDbg) Log.d(TAG, "completeConstruction: E");
+
+ /**
+ * Determine the maximum depth of the state hierarchy
+ * so we can allocate the state stacks.
+ */
+ int maxDepth = 0;
+ for (StateInfo si : mStateInfo.values()) {
+ int depth = 0;
+ for (StateInfo i = si; i != null; depth++) {
+ i = i.parentStateInfo;
+ }
+ if (maxDepth < depth) {
+ maxDepth = depth;
+ }
+ }
+ if (mDbg) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth);
+
+ mStateStack = new StateInfo[maxDepth];
+ mTempStateStack = new StateInfo[maxDepth];
+ setupInitialStateStack();
+
+ /**
+ * Construction is complete call all enter methods
+ * starting at the first entry.
+ */
+ mIsConstructionCompleted = true;
+ invokeEnterMethods(0);
+
+ if (mDbg) Log.d(TAG, "completeConstruction: X");
+ }
+
+ /**
+ * Process the message. If the current state doesn't handle
+ * it, call the states parent and so on. If it is never handled then
+ * call the state machines unhandledMessage method.
+ */
+ private final void processMsg(Message msg) {
+ StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
+ if (mDbg) {
+ Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
+ }
+ while (!curStateInfo.state.processMessage(msg)) {
+ /**
+ * Not processed
+ */
+ curStateInfo = curStateInfo.parentStateInfo;
+ if (curStateInfo == null) {
+ /**
+ * No parents left so it's not handled
+ */
+ mHsm.unhandledMessage(msg);
+ break;
+ }
+ if (mDbg) {
+ Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
+ }
+ }
+
+ /**
+ * Record that we processed the message
+ */
+ if (curStateInfo != null) {
+ HierarchicalState orgState = mStateStack[mStateStackTopIndex].state;
+ mProcessedMessages.add(msg, curStateInfo.state, orgState);
+ } else {
+ mProcessedMessages.add(msg, null, null);
+ }
+ }
+
+ /**
+ * Call the exit method for each state from the top of stack
+ * up to the common ancestor state.
+ */
+ private final void invokeExitMethods(StateInfo commonStateInfo) {
+ while ((mStateStackTopIndex >= 0) &&
+ (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
+ HierarchicalState curState = mStateStack[mStateStackTopIndex].state;
+ if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
+ curState.exit();
+ mStateStack[mStateStackTopIndex].active = false;
+ mStateStackTopIndex -= 1;
+ }
+ }
+
+ /**
+ * Invoke the enter method starting at the entering index to top of state stack
+ */
+ private final void invokeEnterMethods(int stateStackEnteringIndex) {
+ for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
+ if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName());
+ mStateStack[i].state.enter();
+ mStateStack[i].active = true;
+ }
+ }
+
+ /**
+ * Move the deferred message to the front of the message queue.
+ */
+ private final void moveDeferredMessageAtFrontOfQueue() {
+ /**
+ * The oldest messages on the deferred list must be at
+ * the front of the queue so start at the back, which
+ * as the most resent message and end with the oldest
+ * messages at the front of the queue.
+ */
+ for (int i = mDeferredMessages.size() - 1; i >= 0; i-- ) {
+ Message curMsg = mDeferredMessages.get(i);
+ if (mDbg) Log.d(TAG, "moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
+ sendMessageAtFrontOfQueue(curMsg);
+ }
+ mDeferredMessages.clear();
+ }
+
+ /**
+ * Move the contents of the temporary stack to the state stack
+ * reversing the order of the items on the temporary stack as
+ * they are moved.
+ *
+ * @return index into mStateState where entering needs to start
+ */
+ private final int moveTempStateStackToStateStack() {
+ int startingIndex = mStateStackTopIndex + 1;
+ int i = mTempStateStackCount - 1;
+ int j = startingIndex;
+ while (i >= 0) {
+ if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
+ mStateStack[j] = mTempStateStack[i];
+ j += 1;
+ i -= 1;
+ }
+
+ mStateStackTopIndex = j - 1;
+ if (mDbg) {
+ Log.d(TAG, "moveTempStackToStateStack: X mStateStackTop="
+ + mStateStackTopIndex + ",startingIndex=" + startingIndex
+ + ",Top=" + mStateStack[mStateStackTopIndex].state.getName());
+ }
+ return startingIndex;
+ }
+
+ /**
+ * Setup the mTempStateStack with the states we are going to enter.
+ *
+ * This is found by searching up the destState's ancestors for a
+ * state that is already active i.e. StateInfo.active == true.
+ * The destStae and all of its inactive parents will be on the
+ * TempStateStack as the list of states to enter.
+ *
+ * @return StateInfo of the common ancestor for the destState and
+ * current state or null if there is no common parent.
+ */
+ private final StateInfo setupTempStateStackWithStatesToEnter(HierarchicalState destState) {
+ /**
+ * Search up the parent list of the destination state for an active
+ * state. Use a do while() loop as the destState must always be entered
+ * even if it is active. This can happen if we are exiting/entering
+ * the current state.
+ */
+ mTempStateStackCount = 0;
+ StateInfo curStateInfo = mStateInfo.get(destState);
+ do {
+ mTempStateStack[mTempStateStackCount++] = curStateInfo;
+ curStateInfo = curStateInfo.parentStateInfo;
+ } while ((curStateInfo != null) && !curStateInfo.active);
+
+ if (mDbg) {
+ Log.d(TAG, "setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
+ + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
+ }
+ return curStateInfo;
+ }
+
+ /**
+ * Initialize StateStack to mInitialState.
+ */
+ private final void setupInitialStateStack() {
+ if (mDbg) {
+ Log.d(TAG, "setupInitialStateStack: E mInitialState="
+ + mInitialState.getName());
+ }
+
+ StateInfo curStateInfo = mStateInfo.get(mInitialState);
+ for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
+ mTempStateStack[mTempStateStackCount] = curStateInfo;
+ curStateInfo = curStateInfo.parentStateInfo;
+ }
+
+ // Empty the StateStack
+ mStateStackTopIndex = -1;
+
+ moveTempStateStackToStateStack();
+ }
+
+ /**
+ * @return current state
+ */
+ private final HierarchicalState getCurrentState() {
+ return mStateStack[mStateStackTopIndex].state;
+ }
+
+ /**
+ * Add a new state to the state machine. Bottom up addition
+ * of states is allowed but the same state may only exist
+ * in one hierarchy.
+ *
+ * @param state the state to add
+ * @param parent the parent of state
+ * @return stateInfo for this state
+ */
+ private final StateInfo addState(HierarchicalState state, HierarchicalState parent) {
+ if (mDbg) {
+ Log.d(TAG, "addStateInternal: E state=" + state.getName()
+ + ",parent=" + ((parent == null) ? "" : parent.getName()));
+ }
+ StateInfo parentStateInfo = null;
+ if (parent != null) {
+ parentStateInfo = mStateInfo.get(parent);
+ if (parentStateInfo == null) {
+ // Recursively add our parent as it's not been added yet.
+ parentStateInfo = addState(parent, null);
+ }
+ }
+ StateInfo stateInfo = mStateInfo.get(state);
+ if (stateInfo == null) {
+ stateInfo = new StateInfo();
+ mStateInfo.put(state, stateInfo);
+ }
+
+ // Validate that we aren't adding the same state in two different hierarchies.
+ if ((stateInfo.parentStateInfo != null) &&
+ (stateInfo.parentStateInfo != parentStateInfo)) {
+ throw new RuntimeException("state already added");
+ }
+ stateInfo.state = state;
+ stateInfo.parentStateInfo = parentStateInfo;
+ stateInfo.active = false;
+ if (mDbg) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo);
+ return stateInfo;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param looper for dispatching messages
+ * @param hsm the hierarchical state machine
+ */
+ private HsmHandler(Looper looper, HierarchicalStateMachine hsm) {
+ super(looper);
+ mHsm = hsm;
+
+ addState(mHaltingState, null);
+ }
+
+ /** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */
+ private final void setInitialState(HierarchicalState initialState) {
+ if (mDbg) Log.d(TAG, "setInitialState: initialState" + initialState.getName());
+ mInitialState = initialState;
+ }
+
+ /** @see HierarchicalStateMachine#transitionTo(HierarchicalState) */
+ private final void transitionTo(HierarchicalState destState) {
+ if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + destState.getName());
+ mDestState = destState;
+ }
+
+ /** @see HierarchicalStateMachine#deferMessage(Message) */
+ private final void deferMessage(Message msg) {
+ if (mDbg) Log.d(TAG, "deferMessage: msg=" + msg.what);
+
+ /* Copy the "msg" to "newMsg" as "msg" will be recycled */
+ Message newMsg = obtainMessage();
+ newMsg.copyFrom(msg);
+
+ mDeferredMessages.add(newMsg);
+ }
+
+ /** @see HierarchicalStateMachine#isDbg() */
+ private final boolean isDbg() {
+ return mDbg;
+ }
+
+ /** @see HierarchicalStateMachine#setDbg(boolean) */
+ private final void setDbg(boolean dbg) {
+ mDbg = dbg;
+ }
+
+ /** @see HierarchicalStateMachine#setProcessedMessagesSize(int) */
+ private final void setProcessedMessagesSize(int maxSize) {
+ mProcessedMessages.setSize(maxSize);
+ }
+
+ /** @see HierarchicalStateMachine#getProcessedMessagesSize() */
+ private final int getProcessedMessagesSize() {
+ return mProcessedMessages.size();
+ }
+
+ /** @see HierarchicalStateMachine#getProcessedMessagesCount() */
+ private final int getProcessedMessagesCount() {
+ return mProcessedMessages.count();
+ }
+
+ /** @see HierarchicalStateMachine#getProcessedMessage(int) */
+ private final ProcessedMessages.Info getProcessedMessage(int index) {
+ return mProcessedMessages.get(index);
+ }
+
+ }
+
+ private HsmHandler mHsmHandler;
+ private HandlerThread mHsmThread;
+
+ /**
+ * Initialize.
+ *
+ * @param looper for this state machine
+ * @param name of the state machine
+ */
+ private void initStateMachine(Looper looper, String name) {
+ mName = name;
+ mHsmHandler = new HsmHandler(looper, this);
+ }
+
+ /**
+ * Constructor creates an HSM with its own thread.
+ *
+ * @param name of the state machine
+ */
+ protected HierarchicalStateMachine(String name) {
+ mHsmThread = new HandlerThread(name);
+ mHsmThread.start();
+ Looper looper = mHsmThread.getLooper();
+
+ initStateMachine(looper, name);
+ }
+
+ /**
+ * Constructor creates an HSMStateMachine using the looper.
+ *
+ * @param name of the state machine
+ */
+ protected HierarchicalStateMachine(Looper looper, String name) {
+ initStateMachine(looper, name);
+ }
+
+ /**
+ * Add a new state to the state machine
+ * @param state the state to add
+ * @param parent the parent of state
+ */
+ protected final void addState(HierarchicalState state, HierarchicalState parent) {
+ mHsmHandler.addState(state, parent);
+ }
+ /**
+ * @return current state
+ */
+ protected final HierarchicalState getCurrentState() {
+ return mHsmHandler.getCurrentState();
+ }
+
+
+ /**
+ * Add a new state to the state machine, parent will be null
+ * @param state to add
+ */
+ protected final void addState(HierarchicalState state) {
+ mHsmHandler.addState(state, null);
+ }
+
+ /**
+ * Set the initial state. This must be invoked before
+ * and messages are sent to the state machine.
+ *
+ * @param initialState is the state which will receive the first message.
+ */
+ protected final void setInitialState(HierarchicalState initialState) {
+ mHsmHandler.setInitialState(initialState);
+ }
+
+ /**
+ * transition to destination state. Upon returning
+ * from processMessage the current state's exit will
+ * be executed and upon the next message arriving
+ * destState.enter will be invoked.
+ *
+ * @param destState will be the state that receives the next message.
+ */
+ protected final void transitionTo(HierarchicalState destState) {
+ mHsmHandler.transitionTo(destState);
+ }
+
+ /**
+ * transition to halt state. Upon returning
+ * from processMessage we will exit all current
+ * states, execute the halting() method and then
+ * all subsequent messages haltedProcessMesage
+ * will be called.
+ */
+ protected final void transitionToHaltingState() {
+ mHsmHandler.transitionTo(mHsmHandler.mHaltingState);
+ }
+
+ /**
+ * Defer this message until next state transition.
+ * Upon transitioning all deferred messages will be
+ * placed on the queue and reprocessed in the original
+ * order. (i.e. The next state the oldest messages will
+ * be processed first)
+ *
+ * @param msg is deferred until the next transition.
+ */
+ protected final void deferMessage(Message msg) {
+ mHsmHandler.deferMessage(msg);
+ }
+
+
+ /**
+ * Called when message wasn't handled
+ *
+ * @param msg that couldn't be handled.
+ */
+ protected void unhandledMessage(Message msg) {
+ Log.e(TAG, "unhandledMessage: msg.what=" + msg.what);
+ }
+
+ /**
+ * Called for any message that is received after
+ * transitionToHalting is called.
+ */
+ protected void haltedProcessMessage(Message msg) {
+ }
+
+ /**
+ * Called after the message that called transitionToHalting
+ * is called and should be overridden by StateMachine's that
+ * call transitionToHalting.
+ */
+ protected void halting() {
+ }
+
+ /**
+ * @return the name
+ */
+ public final String getName() {
+ return mName;
+ }
+
+ /**
+ * Set size of messages to maintain and clears all current messages.
+ *
+ * @param maxSize number of messages to maintain at anyone time.
+ */
+ public final void setProcessedMessagesSize(int maxSize) {
+ mHsmHandler.setProcessedMessagesSize(maxSize);
+ }
+
+ /**
+ * @return number of messages processed
+ */
+ public final int getProcessedMessagesSize() {
+ return mHsmHandler.getProcessedMessagesSize();
+ }
+
+ /**
+ * @return the total number of messages processed
+ */
+ public final int getProcessedMessagesCount() {
+ return mHsmHandler.getProcessedMessagesCount();
+ }
+
+ /**
+ * @return a processed message
+ */
+ public final ProcessedMessages.Info getProcessedMessage(int index) {
+ return mHsmHandler.getProcessedMessage(index);
+ }
+
+ /**
+ * @return Handler
+ */
+ public final Handler getHandler() {
+ return mHsmHandler;
+ }
+
+ /**
+ * Get a message and set Message.target = this.
+ *
+ * @return message
+ */
+ public final Message obtainMessage()
+ {
+ return Message.obtain(mHsmHandler);
+ }
+
+ /**
+ * Get a message and set Message.target = this and what
+ *
+ * @param what is the assigned to Message.what.
+ * @return message
+ */
+ public final Message obtainMessage(int what) {
+ return Message.obtain(mHsmHandler, what);
+ }
+
+ /**
+ * Get a message and set Message.target = this,
+ * what and obj.
+ *
+ * @param what is the assigned to Message.what.
+ * @param obj is assigned to Message.obj.
+ * @return message
+ */
+ public final Message obtainMessage(int what, Object obj)
+ {
+ return Message.obtain(mHsmHandler, what, obj);
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ */
+ public final void sendMessage(Message msg) {
+ mHsmHandler.sendMessage(msg);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ */
+ public final void sendMessageDelayed(Message msg, long delayMillis) {
+ mHsmHandler.sendMessageDelayed(msg, delayMillis);
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of HierarchicalStateMachine.
+ */
+ protected final void sendMessageAtFrontOfQueue(Message msg) {
+ mHsmHandler.sendMessageAtFrontOfQueue(msg);
+ }
+
+ /**
+ * @return if debugging is enabled
+ */
+ public boolean isDbg() {
+ return mHsmHandler.isDbg();
+ }
+
+ /**
+ * Set debug enable/disabled.
+ *
+ * @param dbg is true to enable debugging.
+ */
+ public void setDbg(boolean dbg) {
+ mHsmHandler.setDbg(dbg);
+ }
+
+ /**
+ * Start the state machine.
+ */
+ public void start() {
+ /** Send the complete construction message */
+ mHsmHandler.completeConstruction();
+ }
+}
diff --git a/core/java/com/android/internal/util/ProcessedMessages.java b/core/java/com/android/internal/util/ProcessedMessages.java
new file mode 100644
index 0000000..244474e
--- /dev/null
+++ b/core/java/com/android/internal/util/ProcessedMessages.java
@@ -0,0 +1,198 @@
+/**
+ * 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.internal.util;
+
+import android.os.Message;
+
+import java.util.Vector;
+
+/**
+ * {@hide}
+ *
+ * A list of messages recently processed by the state machine.
+ *
+ * The class maintains a list of messages that have been most
+ * recently processed. The list is finite and may be set in the
+ * constructor or by calling setSize. The public interface also
+ * includes size which returns the number of recent messages,
+ * count which is the number of message processed since the
+ * the last setSize, get which returns a processed message and
+ * add which adds a processed messaged.
+ */
+public class ProcessedMessages {
+
+ public static final int DEFAULT_SIZE = 20;
+
+ /**
+ * The information maintained for a processed message.
+ */
+ public class Info {
+ private int what;
+ private HierarchicalState state;
+ private HierarchicalState orgState;
+
+ /**
+ * Constructor
+ * @param message
+ * @param state that handled the message
+ * @param orgState is the first state the received the message but
+ * did not processes the message.
+ */
+ Info(Message message, HierarchicalState state, HierarchicalState orgState) {
+ update(message, state, orgState);
+ }
+
+ /**
+ * Update the information in the record.
+ * @param state that handled the message
+ * @param orgState is the first state the received the message but
+ * did not processes the message.
+ */
+ public void update(Message message, HierarchicalState state, HierarchicalState orgState) {
+ this.what = message.what;
+ this.state = state;
+ this.orgState = orgState;
+ }
+
+ /**
+ * @return the command that was executing
+ */
+ public int getWhat() {
+ return what;
+ }
+
+ /**
+ * @return the state that handled this message
+ */
+ public HierarchicalState getState() {
+ return state;
+ }
+
+ /**
+ * @return the original state that received the message.
+ */
+ public HierarchicalState getOriginalState() {
+ return orgState;
+ }
+
+ /**
+ * @return as string
+ */
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("what=");
+ sb.append(what);
+ sb.append(" state=");
+ sb.append(cn(state));
+ sb.append(" orgState=");
+ sb.append(cn(orgState));
+ return sb.toString();
+ }
+
+ /**
+ * @return an objects class name
+ */
+ private String cn(Object n) {
+ if (n == null) {
+ return "null";
+ } else {
+ String name = n.getClass().getName();
+ int lastDollar = name.lastIndexOf('$');
+ return name.substring(lastDollar + 1);
+ }
+ }
+ }
+
+ private Vector<Info> mMessages = new Vector<Info>();
+ private int mMaxSize = DEFAULT_SIZE;
+ private int mOldestIndex = 0;
+ private int mCount = 0;
+
+ /**
+ * Constructor
+ */
+ ProcessedMessages() {
+ }
+
+ ProcessedMessages(int maxSize) {
+ setSize(maxSize);
+ }
+
+ /**
+ * Set size of messages to maintain and clears all current messages.
+ *
+ * @param maxSize number of messages to maintain at anyone time.
+ */
+ void setSize(int maxSize) {
+ mMaxSize = maxSize;
+ mCount = 0;
+ mMessages.clear();
+ }
+
+ /**
+ * @return the number of recent messages.
+ */
+ int size() {
+ return mMessages.size();
+ }
+
+ /**
+ * @return the total number of messages processed since size was set.
+ */
+ int count() {
+ return mCount;
+ }
+
+ /**
+ * @return the information on a particular record. 0 is the oldest
+ * record and size()-1 is the newest record. If the index is to
+ * large null is returned.
+ */
+ Info get(int index) {
+ int nextIndex = mOldestIndex + index;
+ if (nextIndex >= mMaxSize) {
+ nextIndex -= mMaxSize;
+ }
+ if (nextIndex >= size()) {
+ return null;
+ } else {
+ return mMessages.get(nextIndex);
+ }
+ }
+
+ /**
+ * Add a processed message.
+ *
+ * @param message
+ * @param state that handled the message
+ * @param orgState is the first state the received the message but
+ * did not processes the message.
+ */
+ void add(Message message, HierarchicalState state, HierarchicalState orgState) {
+ mCount += 1;
+ if (mMessages.size() < mMaxSize) {
+ mMessages.add(new Info(message, state, orgState));
+ } else {
+ Info info = mMessages.get(mOldestIndex);
+ mOldestIndex += 1;
+ if (mOldestIndex >= mMaxSize) {
+ mOldestIndex = 0;
+ }
+ info.update(message, state, orgState);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
deleted file mode 100644
index 948e313..0000000
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ /dev/null
@@ -1,796 +0,0 @@
-/*
- * 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 com.android.internal.util;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import android.util.Xml;
-
-/** {@hide} */
-public class XmlUtils
-{
-
- public static void skipCurrentTag(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- int outerDepth = parser.getDepth();
- int type;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG
- || parser.getDepth() > outerDepth)) {
- }
- }
-
- public static final int
- convertValueToList(CharSequence value, String[] options, int defaultValue)
- {
- if (null != value) {
- for (int i = 0; i < options.length; i++) {
- if (value.equals(options[i]))
- return i;
- }
- }
-
- return defaultValue;
- }
-
- public static final boolean
- convertValueToBoolean(CharSequence value, boolean defaultValue)
- {
- boolean result = false;
-
- if (null == value)
- return defaultValue;
-
- if (value.equals("1")
- || value.equals("true")
- || value.equals("TRUE"))
- result = true;
-
- return result;
- }
-
- public static final int
- convertValueToInt(CharSequence charSeq, int defaultValue)
- {
- if (null == charSeq)
- return defaultValue;
-
- String nm = charSeq.toString();
-
- // XXX This code is copied from Integer.decode() so we don't
- // have to instantiate an Integer!
-
- int value;
- int sign = 1;
- int index = 0;
- int len = nm.length();
- int base = 10;
-
- if ('-' == nm.charAt(0)) {
- sign = -1;
- index++;
- }
-
- if ('0' == nm.charAt(index)) {
- // Quick check for a zero by itself
- if (index == (len - 1))
- return 0;
-
- char c = nm.charAt(index + 1);
-
- if ('x' == c || 'X' == c) {
- index += 2;
- base = 16;
- } else {
- index++;
- base = 8;
- }
- }
- else if ('#' == nm.charAt(index))
- {
- index++;
- base = 16;
- }
-
- return Integer.parseInt(nm.substring(index), base) * sign;
- }
-
- public static final int
- convertValueToUnsignedInt(String value, int defaultValue)
- {
- if (null == value)
- return defaultValue;
-
- return parseUnsignedIntAttribute(value);
- }
-
- public static final int
- parseUnsignedIntAttribute(CharSequence charSeq)
- {
- String value = charSeq.toString();
-
- long bits;
- int index = 0;
- int len = value.length();
- int base = 10;
-
- if ('0' == value.charAt(index)) {
- // Quick check for zero by itself
- if (index == (len - 1))
- return 0;
-
- char c = value.charAt(index + 1);
-
- if ('x' == c || 'X' == c) { // check for hex
- index += 2;
- base = 16;
- } else { // check for octal
- index++;
- base = 8;
- }
- } else if ('#' == value.charAt(index)) {
- index++;
- base = 16;
- }
-
- return (int) Long.parseLong(value.substring(index), base);
- }
-
- /**
- * Flatten a Map into an output stream as XML. The map can later be
- * read back with readMapXml().
- *
- * @param val The map to be flattened.
- * @param out Where to write the XML data.
- *
- * @see #writeMapXml(Map, String, XmlSerializer)
- * @see #writeListXml
- * @see #writeValueXml
- * @see #readMapXml
- */
- public static final void writeMapXml(Map val, OutputStream out)
- throws XmlPullParserException, java.io.IOException {
- XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(out, "utf-8");
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- writeMapXml(val, null, serializer);
- serializer.endDocument();
- }
-
- /**
- * Flatten a List into an output stream as XML. The list can later be
- * read back with readListXml().
- *
- * @param val The list to be flattened.
- * @param out Where to write the XML data.
- *
- * @see #writeListXml(List, String, XmlSerializer)
- * @see #writeMapXml
- * @see #writeValueXml
- * @see #readListXml
- */
- public static final void writeListXml(List val, OutputStream out)
- throws XmlPullParserException, java.io.IOException
- {
- XmlSerializer serializer = Xml.newSerializer();
- serializer.setOutput(out, "utf-8");
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- writeListXml(val, null, serializer);
- serializer.endDocument();
- }
-
- /**
- * Flatten a Map into an XmlSerializer. The map can later be read back
- * with readThisMapXml().
- *
- * @param val The map to be flattened.
- * @param name Name attribute to include with this list's tag, or null for
- * none.
- * @param out XmlSerializer to write the map into.
- *
- * @see #writeMapXml(Map, OutputStream)
- * @see #writeListXml
- * @see #writeValueXml
- * @see #readMapXml
- */
- public static final void writeMapXml(Map val, String name, XmlSerializer out)
- throws XmlPullParserException, java.io.IOException
- {
- if (val == null) {
- out.startTag(null, "null");
- out.endTag(null, "null");
- return;
- }
-
- Set s = val.entrySet();
- Iterator i = s.iterator();
-
- out.startTag(null, "map");
- if (name != null) {
- out.attribute(null, "name", name);
- }
-
- while (i.hasNext()) {
- Map.Entry e = (Map.Entry)i.next();
- writeValueXml(e.getValue(), (String)e.getKey(), out);
- }
-
- out.endTag(null, "map");
- }
-
- /**
- * Flatten a List into an XmlSerializer. The list can later be read back
- * with readThisListXml().
- *
- * @param val The list to be flattened.
- * @param name Name attribute to include with this list's tag, or null for
- * none.
- * @param out XmlSerializer to write the list into.
- *
- * @see #writeListXml(List, OutputStream)
- * @see #writeMapXml
- * @see #writeValueXml
- * @see #readListXml
- */
- public static final void writeListXml(List val, String name, XmlSerializer out)
- throws XmlPullParserException, java.io.IOException
- {
- if (val == null) {
- out.startTag(null, "null");
- out.endTag(null, "null");
- return;
- }
-
- out.startTag(null, "list");
- if (name != null) {
- out.attribute(null, "name", name);
- }
-
- int N = val.size();
- int i=0;
- while (i < N) {
- writeValueXml(val.get(i), null, out);
- i++;
- }
-
- out.endTag(null, "list");
- }
-
- /**
- * Flatten a byte[] into an XmlSerializer. The list can later be read back
- * with readThisByteArrayXml().
- *
- * @param val The byte array to be flattened.
- * @param name Name attribute to include with this array's tag, or null for
- * none.
- * @param out XmlSerializer to write the array into.
- *
- * @see #writeMapXml
- * @see #writeValueXml
- */
- public static final void writeByteArrayXml(byte[] val, String name,
- XmlSerializer out)
- throws XmlPullParserException, java.io.IOException {
-
- if (val == null) {
- out.startTag(null, "null");
- out.endTag(null, "null");
- return;
- }
-
- out.startTag(null, "byte-array");
- if (name != null) {
- out.attribute(null, "name", name);
- }
-
- final int N = val.length;
- out.attribute(null, "num", Integer.toString(N));
-
- StringBuilder sb = new StringBuilder(val.length*2);
- for (int i=0; i<N; i++) {
- int b = val[i];
- int h = b>>4;
- sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
- h = b&0xff;
- sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
- }
-
- out.text(sb.toString());
-
- out.endTag(null, "byte-array");
- }
-
- /**
- * Flatten an int[] into an XmlSerializer. The list can later be read back
- * with readThisIntArrayXml().
- *
- * @param val The int array to be flattened.
- * @param name Name attribute to include with this array's tag, or null for
- * none.
- * @param out XmlSerializer to write the array into.
- *
- * @see #writeMapXml
- * @see #writeValueXml
- * @see #readThisIntArrayXml
- */
- public static final void writeIntArrayXml(int[] val, String name,
- XmlSerializer out)
- throws XmlPullParserException, java.io.IOException {
-
- if (val == null) {
- out.startTag(null, "null");
- out.endTag(null, "null");
- return;
- }
-
- out.startTag(null, "int-array");
- if (name != null) {
- out.attribute(null, "name", name);
- }
-
- final int N = val.length;
- out.attribute(null, "num", Integer.toString(N));
-
- for (int i=0; i<N; i++) {
- out.startTag(null, "item");
- out.attribute(null, "value", Integer.toString(val[i]));
- out.endTag(null, "item");
- }
-
- out.endTag(null, "int-array");
- }
-
- /**
- * Flatten an object's value into an XmlSerializer. The value can later
- * be read back with readThisValueXml().
- *
- * Currently supported value types are: null, String, Integer, Long,
- * Float, Double Boolean, Map, List.
- *
- * @param v The object to be flattened.
- * @param name Name attribute to include with this value's tag, or null
- * for none.
- * @param out XmlSerializer to write the object into.
- *
- * @see #writeMapXml
- * @see #writeListXml
- * @see #readValueXml
- */
- public static final void writeValueXml(Object v, String name, XmlSerializer out)
- throws XmlPullParserException, java.io.IOException
- {
- String typeStr;
- if (v == null) {
- out.startTag(null, "null");
- if (name != null) {
- out.attribute(null, "name", name);
- }
- out.endTag(null, "null");
- return;
- } else if (v instanceof String) {
- out.startTag(null, "string");
- if (name != null) {
- out.attribute(null, "name", name);
- }
- out.text(v.toString());
- out.endTag(null, "string");
- return;
- } else if (v instanceof Integer) {
- typeStr = "int";
- } else if (v instanceof Long) {
- typeStr = "long";
- } else if (v instanceof Float) {
- typeStr = "float";
- } else if (v instanceof Double) {
- typeStr = "double";
- } else if (v instanceof Boolean) {
- typeStr = "boolean";
- } else if (v instanceof byte[]) {
- writeByteArrayXml((byte[])v, name, out);
- return;
- } else if (v instanceof int[]) {
- writeIntArrayXml((int[])v, name, out);
- return;
- } else if (v instanceof Map) {
- writeMapXml((Map)v, name, out);
- return;
- } else if (v instanceof List) {
- writeListXml((List)v, name, out);
- return;
- } else if (v instanceof CharSequence) {
- // XXX This is to allow us to at least write something if
- // we encounter styled text... but it means we will drop all
- // of the styling information. :(
- out.startTag(null, "string");
- if (name != null) {
- out.attribute(null, "name", name);
- }
- out.text(v.toString());
- out.endTag(null, "string");
- return;
- } else {
- throw new RuntimeException("writeValueXml: unable to write value " + v);
- }
-
- out.startTag(null, typeStr);
- if (name != null) {
- out.attribute(null, "name", name);
- }
- out.attribute(null, "value", v.toString());
- out.endTag(null, typeStr);
- }
-
- /**
- * Read a HashMap from an InputStream containing XML. The stream can
- * previously have been written by writeMapXml().
- *
- * @param in The InputStream from which to read.
- *
- * @return HashMap The resulting map.
- *
- * @see #readListXml
- * @see #readValueXml
- * @see #readThisMapXml
- * #see #writeMapXml
- */
- public static final HashMap readMapXml(InputStream in)
- throws XmlPullParserException, java.io.IOException
- {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(in, null);
- return (HashMap)readValueXml(parser, new String[1]);
- }
-
- /**
- * Read an ArrayList from an InputStream containing XML. The stream can
- * previously have been written by writeListXml().
- *
- * @param in The InputStream from which to read.
- *
- * @return HashMap The resulting list.
- *
- * @see #readMapXml
- * @see #readValueXml
- * @see #readThisListXml
- * @see #writeListXml
- */
- public static final ArrayList readListXml(InputStream in)
- throws XmlPullParserException, java.io.IOException
- {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(in, null);
- return (ArrayList)readValueXml(parser, new String[1]);
- }
-
- /**
- * Read a HashMap object from an XmlPullParser. The XML data could
- * previously have been generated by writeMapXml(). The XmlPullParser
- * must be positioned <em>after</em> the tag that begins the map.
- *
- * @param parser The XmlPullParser from which to read the map data.
- * @param endTag Name of the tag that will end the map, usually "map".
- * @param name An array of one string, used to return the name attribute
- * of the map's tag.
- *
- * @return HashMap The newly generated map.
- *
- * @see #readMapXml
- */
- public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name)
- throws XmlPullParserException, java.io.IOException
- {
- HashMap map = new HashMap();
-
- int eventType = parser.getEventType();
- do {
- if (eventType == parser.START_TAG) {
- Object val = readThisValueXml(parser, name);
- if (name[0] != null) {
- //System.out.println("Adding to map: " + name + " -> " + val);
- map.put(name[0], val);
- } else {
- throw new XmlPullParserException(
- "Map value without name attribute: " + parser.getName());
- }
- } else if (eventType == parser.END_TAG) {
- if (parser.getName().equals(endTag)) {
- return map;
- }
- throw new XmlPullParserException(
- "Expected " + endTag + " end tag at: " + parser.getName());
- }
- eventType = parser.next();
- } while (eventType != parser.END_DOCUMENT);
-
- throw new XmlPullParserException(
- "Document ended before " + endTag + " end tag");
- }
-
- /**
- * Read an ArrayList object from an XmlPullParser. The XML data could
- * previously have been generated by writeListXml(). The XmlPullParser
- * must be positioned <em>after</em> the tag that begins the list.
- *
- * @param parser The XmlPullParser from which to read the list data.
- * @param endTag Name of the tag that will end the list, usually "list".
- * @param name An array of one string, used to return the name attribute
- * of the list's tag.
- *
- * @return HashMap The newly generated list.
- *
- * @see #readListXml
- */
- public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name)
- throws XmlPullParserException, java.io.IOException
- {
- ArrayList list = new ArrayList();
-
- int eventType = parser.getEventType();
- do {
- if (eventType == parser.START_TAG) {
- Object val = readThisValueXml(parser, name);
- list.add(val);
- //System.out.println("Adding to list: " + val);
- } else if (eventType == parser.END_TAG) {
- if (parser.getName().equals(endTag)) {
- return list;
- }
- throw new XmlPullParserException(
- "Expected " + endTag + " end tag at: " + parser.getName());
- }
- eventType = parser.next();
- } while (eventType != parser.END_DOCUMENT);
-
- throw new XmlPullParserException(
- "Document ended before " + endTag + " end tag");
- }
-
- /**
- * Read an int[] object from an XmlPullParser. The XML data could
- * previously have been generated by writeIntArrayXml(). The XmlPullParser
- * must be positioned <em>after</em> the tag that begins the list.
- *
- * @param parser The XmlPullParser from which to read the list data.
- * @param endTag Name of the tag that will end the list, usually "list".
- * @param name An array of one string, used to return the name attribute
- * of the list's tag.
- *
- * @return Returns a newly generated int[].
- *
- * @see #readListXml
- */
- public static final int[] readThisIntArrayXml(XmlPullParser parser,
- String endTag, String[] name)
- throws XmlPullParserException, java.io.IOException {
-
- int num;
- try {
- num = Integer.parseInt(parser.getAttributeValue(null, "num"));
- } catch (NullPointerException e) {
- throw new XmlPullParserException(
- "Need num attribute in byte-array");
- } catch (NumberFormatException e) {
- throw new XmlPullParserException(
- "Not a number in num attribute in byte-array");
- }
-
- int[] array = new int[num];
- int i = 0;
-
- int eventType = parser.getEventType();
- do {
- if (eventType == parser.START_TAG) {
- if (parser.getName().equals("item")) {
- try {
- array[i] = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } catch (NullPointerException e) {
- throw new XmlPullParserException(
- "Need value attribute in item");
- } catch (NumberFormatException e) {
- throw new XmlPullParserException(
- "Not a number in value attribute in item");
- }
- } else {
- throw new XmlPullParserException(
- "Expected item tag at: " + parser.getName());
- }
- } else if (eventType == parser.END_TAG) {
- if (parser.getName().equals(endTag)) {
- return array;
- } else if (parser.getName().equals("item")) {
- i++;
- } else {
- throw new XmlPullParserException(
- "Expected " + endTag + " end tag at: "
- + parser.getName());
- }
- }
- eventType = parser.next();
- } while (eventType != parser.END_DOCUMENT);
-
- throw new XmlPullParserException(
- "Document ended before " + endTag + " end tag");
- }
-
- /**
- * Read a flattened object from an XmlPullParser. The XML data could
- * previously have been written with writeMapXml(), writeListXml(), or
- * writeValueXml(). The XmlPullParser must be positioned <em>at</em> the
- * tag that defines the value.
- *
- * @param parser The XmlPullParser from which to read the object.
- * @param name An array of one string, used to return the name attribute
- * of the value's tag.
- *
- * @return Object The newly generated value object.
- *
- * @see #readMapXml
- * @see #readListXml
- * @see #writeValueXml
- */
- public static final Object readValueXml(XmlPullParser parser, String[] name)
- throws XmlPullParserException, java.io.IOException
- {
- int eventType = parser.getEventType();
- do {
- if (eventType == parser.START_TAG) {
- return readThisValueXml(parser, name);
- } else if (eventType == parser.END_TAG) {
- throw new XmlPullParserException(
- "Unexpected end tag at: " + parser.getName());
- } else if (eventType == parser.TEXT) {
- throw new XmlPullParserException(
- "Unexpected text: " + parser.getText());
- }
- eventType = parser.next();
- } while (eventType != parser.END_DOCUMENT);
-
- throw new XmlPullParserException(
- "Unexpected end of document");
- }
-
- private static final Object readThisValueXml(XmlPullParser parser, String[] name)
- throws XmlPullParserException, java.io.IOException
- {
- final String valueName = parser.getAttributeValue(null, "name");
- final String tagName = parser.getName();
-
- //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName);
-
- Object res;
-
- if (tagName.equals("null")) {
- res = null;
- } else if (tagName.equals("string")) {
- String value = "";
- int eventType;
- while ((eventType = parser.next()) != parser.END_DOCUMENT) {
- if (eventType == parser.END_TAG) {
- if (parser.getName().equals("string")) {
- name[0] = valueName;
- //System.out.println("Returning value for " + valueName + ": " + value);
- return value;
- }
- throw new XmlPullParserException(
- "Unexpected end tag in <string>: " + parser.getName());
- } else if (eventType == parser.TEXT) {
- value += parser.getText();
- } else if (eventType == parser.START_TAG) {
- throw new XmlPullParserException(
- "Unexpected start tag in <string>: " + parser.getName());
- }
- }
- throw new XmlPullParserException(
- "Unexpected end of document in <string>");
- } else if (tagName.equals("int")) {
- res = Integer.parseInt(parser.getAttributeValue(null, "value"));
- } else if (tagName.equals("long")) {
- res = Long.valueOf(parser.getAttributeValue(null, "value"));
- } else if (tagName.equals("float")) {
- res = new Float(parser.getAttributeValue(null, "value"));
- } else if (tagName.equals("double")) {
- res = new Double(parser.getAttributeValue(null, "value"));
- } else if (tagName.equals("boolean")) {
- res = Boolean.valueOf(parser.getAttributeValue(null, "value"));
- } else if (tagName.equals("int-array")) {
- parser.next();
- res = readThisIntArrayXml(parser, "int-array", name);
- name[0] = valueName;
- //System.out.println("Returning value for " + valueName + ": " + res);
- return res;
- } else if (tagName.equals("map")) {
- parser.next();
- res = readThisMapXml(parser, "map", name);
- name[0] = valueName;
- //System.out.println("Returning value for " + valueName + ": " + res);
- return res;
- } else if (tagName.equals("list")) {
- parser.next();
- res = readThisListXml(parser, "list", name);
- name[0] = valueName;
- //System.out.println("Returning value for " + valueName + ": " + res);
- return res;
- } else {
- throw new XmlPullParserException(
- "Unknown tag: " + tagName);
- }
-
- // Skip through to end tag.
- int eventType;
- while ((eventType = parser.next()) != parser.END_DOCUMENT) {
- if (eventType == parser.END_TAG) {
- if (parser.getName().equals(tagName)) {
- name[0] = valueName;
- //System.out.println("Returning value for " + valueName + ": " + res);
- return res;
- }
- throw new XmlPullParserException(
- "Unexpected end tag in <" + tagName + ">: " + parser.getName());
- } else if (eventType == parser.TEXT) {
- throw new XmlPullParserException(
- "Unexpected text in <" + tagName + ">: " + parser.getName());
- } else if (eventType == parser.START_TAG) {
- throw new XmlPullParserException(
- "Unexpected start tag in <" + tagName + ">: " + parser.getName());
- }
- }
- throw new XmlPullParserException(
- "Unexpected end of document in <" + tagName + ">");
- }
-
- public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException
- {
- int type;
- while ((type=parser.next()) != parser.START_TAG
- && type != parser.END_DOCUMENT) {
- ;
- }
-
- if (type != parser.START_TAG) {
- throw new XmlPullParserException("No start tag found");
- }
-
- if (!parser.getName().equals(firstElementName)) {
- throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
- ", expected " + firstElementName);
- }
- }
-
- public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException
- {
- int type;
- while ((type=parser.next()) != parser.START_TAG
- && type != parser.END_DOCUMENT) {
- ;
- }
- }
-}
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 66e15c1..3c5b422 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -254,7 +254,7 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
if (lp == null) {
// Default layout parameters
lp = new IconMenuView.LayoutParams(
- IconMenuView.LayoutParams.FILL_PARENT, IconMenuView.LayoutParams.FILL_PARENT);
+ IconMenuView.LayoutParams.MATCH_PARENT, IconMenuView.LayoutParams.MATCH_PARENT);
}
// Set the desired width of item
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 58056bd..3c5a753 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -38,8 +38,9 @@ import java.util.List;
public class LockPatternUtils {
private static final String TAG = "LockPatternUtils";
-
+
private static final String LOCK_PATTERN_FILE = "/system/gesture.key";
+ private static final String LOCK_PASSWORD_FILE = "/system/password.key";
/**
* The maximum number of incorrect attempts before the user is prevented
@@ -70,20 +71,32 @@ public class LockPatternUtils {
public static final int MIN_LOCK_PATTERN_SIZE = 4;
/**
+ * Type of password being stored.
+ * pattern = pattern screen
+ * pin = digit-only password
+ * password = alphanumeric password
+ */
+ public static final int MODE_PATTERN = 0;
+ public static final int MODE_PIN = 1;
+ public static final int MODE_PASSWORD = 2;
+
+ /**
* The minimum number of dots the user must include in a wrong pattern
* attempt for it to be counted against the counts that affect
* {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET}
*/
- public static final int MIN_PATTERN_REGISTER_FAIL = 3;
+ public static final int MIN_PATTERN_REGISTER_FAIL = 3;
private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
- private final static String PATTERN_EVER_CHOSEN = "lockscreen.patterneverchosen";
+ private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
+ public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
private final ContentResolver mContentResolver;
private static String sLockPatternFilename;
-
+ private static String sLockPasswordFilename;
+
/**
* @param contentResolver Used to look up and save settings.
*/
@@ -91,16 +104,19 @@ public class LockPatternUtils {
mContentResolver = contentResolver;
// Initialize the location of gesture lock file
if (sLockPatternFilename == null) {
- sLockPatternFilename = android.os.Environment.getDataDirectory()
+ sLockPatternFilename = android.os.Environment.getDataDirectory()
.getAbsolutePath() + LOCK_PATTERN_FILE;
+ sLockPasswordFilename = android.os.Environment.getDataDirectory()
+ .getAbsolutePath() + LOCK_PASSWORD_FILE;
}
+
}
/**
* Check to see if a pattern matches the saved pattern. If no pattern exists,
* always returns true.
* @param pattern The pattern to check.
- * @return Whether the pattern matchees the stored one.
+ * @return Whether the pattern matches the stored one.
*/
public boolean checkPattern(List<LockPatternView.Cell> pattern) {
try {
@@ -122,13 +138,40 @@ public class LockPatternUtils {
}
/**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
+ * Check to see if a password matches the saved password. If no password exists,
+ * always returns true.
+ * @param password The password to check.
+ * @return Whether the password matches the stored one.
*/
- public boolean savedPatternExists() {
+ public boolean checkPassword(String password) {
+ try {
+ // Read all the bytes from the file
+ RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "r");
+ final byte[] stored = new byte[(int) raf.length()];
+ int got = raf.read(stored, 0, stored.length);
+ raf.close();
+ if (got <= 0) {
+ return true;
+ }
+ // Compare the hash from the file with the entered password's hash
+ return Arrays.equals(stored, LockPatternUtils.passwordToHash(password));
+ } catch (FileNotFoundException fnfe) {
+ return true;
+ } catch (IOException ioe) {
+ return true;
+ }
+ }
+
+ /**
+ * Checks to see if the given file exists and contains any data. Returns true if it does,
+ * false otherwise.
+ * @param filename
+ * @return true if file exists and is non-empty.
+ */
+ private boolean nonEmptyFileExists(String filename) {
try {
// Check if we can read a byte from the file
- RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r");
+ RandomAccessFile raf = new RandomAccessFile(filename, "r");
byte first = raf.readByte();
raf.close();
return true;
@@ -140,13 +183,29 @@ public class LockPatternUtils {
}
/**
+ * Check to see if the user has stored a lock pattern.
+ * @return Whether a saved pattern exists.
+ */
+ public boolean savedPatternExists() {
+ return nonEmptyFileExists(sLockPatternFilename);
+ }
+
+ /**
+ * Check to see if the user has stored a lock pattern.
+ * @return Whether a saved pattern exists.
+ */
+ public boolean savedPasswordExists() {
+ return nonEmptyFileExists(sLockPasswordFilename);
+ }
+
+ /**
* Return true if the user has ever chosen a pattern. This is true even if the pattern is
* currently cleared.
*
* @return True if the user has ever chosen a pattern.
*/
public boolean isPatternEverChosen() {
- return getBoolean(PATTERN_EVER_CHOSEN);
+ return getBoolean(PATTERN_EVER_CHOSEN_KEY);
}
/**
@@ -166,7 +225,8 @@ public class LockPatternUtils {
raf.write(hash, 0, hash.length);
}
raf.close();
- setBoolean(PATTERN_EVER_CHOSEN, true);
+ setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
+ setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
} catch (FileNotFoundException fnfe) {
// Cant do much, unless we want to fail over to using the settings provider
Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
@@ -177,6 +237,38 @@ public class LockPatternUtils {
}
/**
+ * Save a lock password.
+ * @param password The password to save
+ */
+ public void saveLockPassword(String password) {
+ // Compute the hash
+ boolean numericHint = password != null ? TextUtils.isDigitsOnly(password) : false;
+ final byte[] hash = LockPatternUtils.passwordToHash(password);
+ try {
+ // Write the hash to file
+ RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw");
+ // Truncate the file if pattern is null, to clear the lock
+ if (password == null) {
+ raf.setLength(0);
+ } else {
+ raf.write(hash, 0, hash.length);
+ }
+ raf.close();
+ setLong(PASSWORD_TYPE_KEY, numericHint ? MODE_PIN : MODE_PASSWORD);
+ } catch (FileNotFoundException fnfe) {
+ // Cant do much, unless we want to fail over to using the settings provider
+ Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
+ } catch (IOException ioe) {
+ // Cant do much
+ Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
+ }
+ }
+
+ public int getPasswordMode() {
+ return (int) getLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
+ }
+
+ /**
* Deserialize a pattern.
* @param string The pattern serialized with {@link #patternToString}
* @return The pattern.
@@ -210,7 +302,7 @@ public class LockPatternUtils {
}
return new String(res);
}
-
+
/*
* Generate an SHA-1 hash for the pattern. Not the most secure, but it is
* at least a second level of protection. First level is that the file
@@ -218,11 +310,11 @@ public class LockPatternUtils {
* @param pattern the gesture pattern.
* @return the hash of the pattern in a byte array.
*/
- static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
+ private static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
if (pattern == null) {
return null;
}
-
+
final int patternSize = pattern.size();
byte[] res = new byte[patternSize];
for (int i = 0; i < patternSize; i++) {
@@ -238,11 +330,55 @@ public class LockPatternUtils {
}
}
+ /*
+ * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
+ * Not the most secure, but it is at least a second level of protection. First level is that
+ * the file is in a location only readable by the system process.
+ * @param password the gesture pattern.
+ * @return the hash of the pattern in a byte array.
+ */
+ public static byte[] passwordToHash(String password) {
+ if (password == null) {
+ return null;
+ }
+ String algo = null;
+ byte[] hashed = null;
+ try {
+ long salt = 0x2374868151054924L; // TODO: make this unique to device
+ byte[] saltedPassword = (password + Long.toString(salt)).getBytes();
+ byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword);
+ byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword);
+ hashed = (toHex(sha1) + toHex(md5)).getBytes();
+ } catch (NoSuchAlgorithmException e) {
+ Log.w(TAG, "Failed to encode string because of missing algorithm: " + algo);
+ }
+ return hashed;
+ }
+
+ private static String toHex(byte[] ary) {
+ final String hex = "0123456789ABCDEF";
+ String ret = "";
+ for (int i = 0; i < ary.length; i++) {
+ ret += hex.charAt((ary[i] >> 4) & 0xf);
+ ret += hex.charAt(ary[i] & 0xf);
+ }
+ return ret;
+ }
+
+ /**
+ * @return Whether the lock password is enabled.
+ */
+ public boolean isLockPasswordEnabled() {
+ long mode = getLong(PASSWORD_TYPE_KEY, 0);
+ return savedPasswordExists() && (mode == MODE_PASSWORD || mode == MODE_PIN);
+ }
+
/**
* @return Whether the lock pattern is enabled.
*/
public boolean isLockPatternEnabled() {
- return getBoolean(Settings.System.LOCK_PATTERN_ENABLED);
+ return getBoolean(Settings.System.LOCK_PATTERN_ENABLED)
+ && getLong(PASSWORD_TYPE_KEY, MODE_PATTERN) == MODE_PATTERN;
}
/**
@@ -361,5 +497,10 @@ public class LockPatternUtils {
android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value);
}
-
+ public boolean isSecure() {
+ long mode = getPasswordMode();
+ boolean secure = mode == MODE_PATTERN && isLockPatternEnabled() && savedPatternExists()
+ || (mode == MODE_PIN || mode == MODE_PASSWORD) && savedPasswordExists();
+ return secure;
+ }
}
diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java
deleted file mode 100644
index ae08eca..0000000
--- a/core/java/com/android/internal/widget/NumberPicker.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.internal.widget;
-
-import android.content.Context;
-import android.os.Handler;
-import android.text.InputFilter;
-import android.text.InputType;
-import android.text.Spanned;
-import android.text.method.NumberKeyListener;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.view.View.OnLongClickListener;
-import android.widget.TextView;
-import android.widget.LinearLayout;
-import android.widget.EditText;
-
-import com.android.internal.R;
-
-public class NumberPicker extends LinearLayout implements OnClickListener,
- OnFocusChangeListener, OnLongClickListener {
-
- public interface OnChangedListener {
- void onChanged(NumberPicker picker, int oldVal, int newVal);
- }
-
- public interface Formatter {
- String toString(int value);
- }
-
- /*
- * Use a custom NumberPicker formatting callback to use two-digit
- * minutes strings like "01". Keeping a static formatter etc. is the
- * most efficient way to do this; it avoids creating temporary objects
- * on every call to format().
- */
- public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER =
- new NumberPicker.Formatter() {
- final StringBuilder mBuilder = new StringBuilder();
- final java.util.Formatter mFmt = new java.util.Formatter(mBuilder);
- final Object[] mArgs = new Object[1];
- public String toString(int value) {
- mArgs[0] = value;
- mBuilder.delete(0, mBuilder.length());
- mFmt.format("%02d", mArgs);
- return mFmt.toString();
- }
- };
-
- private final Handler mHandler;
- private final Runnable mRunnable = new Runnable() {
- public void run() {
- if (mIncrement) {
- changeCurrent(mCurrent + 1);
- mHandler.postDelayed(this, mSpeed);
- } else if (mDecrement) {
- changeCurrent(mCurrent - 1);
- mHandler.postDelayed(this, mSpeed);
- }
- }
- };
-
- private final EditText mText;
- private final InputFilter mNumberInputFilter;
-
- private String[] mDisplayedValues;
- protected int mStart;
- protected int mEnd;
- protected int mCurrent;
- protected int mPrevious;
- private OnChangedListener mListener;
- private Formatter mFormatter;
- private long mSpeed = 300;
-
- private boolean mIncrement;
- private boolean mDecrement;
-
- public NumberPicker(Context context) {
- this(context, null);
- }
-
- public NumberPicker(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- @SuppressWarnings({"UnusedDeclaration"})
- public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
- setOrientation(VERTICAL);
- LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.number_picker, this, true);
- mHandler = new Handler();
- InputFilter inputFilter = new NumberPickerInputFilter();
- mNumberInputFilter = new NumberRangeKeyListener();
- mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
- mIncrementButton.setOnClickListener(this);
- mIncrementButton.setOnLongClickListener(this);
- mIncrementButton.setNumberPicker(this);
- mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
- mDecrementButton.setOnClickListener(this);
- mDecrementButton.setOnLongClickListener(this);
- mDecrementButton.setNumberPicker(this);
-
- mText = (EditText) findViewById(R.id.timepicker_input);
- mText.setOnFocusChangeListener(this);
- mText.setFilters(new InputFilter[] {inputFilter});
- mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
-
- if (!isEnabled()) {
- setEnabled(false);
- }
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
- mIncrementButton.setEnabled(enabled);
- mDecrementButton.setEnabled(enabled);
- mText.setEnabled(enabled);
- }
-
- public void setOnChangeListener(OnChangedListener listener) {
- mListener = listener;
- }
-
- public void setFormatter(Formatter formatter) {
- mFormatter = formatter;
- }
-
- /**
- * Set the range of numbers allowed for the number picker. The current
- * value will be automatically set to the start.
- *
- * @param start the start of the range (inclusive)
- * @param end the end of the range (inclusive)
- */
- public void setRange(int start, int end) {
- mStart = start;
- mEnd = end;
- mCurrent = start;
- updateView();
- }
-
- /**
- * Set the range of numbers allowed for the number picker. The current
- * value will be automatically set to the start. Also provide a mapping
- * for values used to display to the user.
- *
- * @param start the start of the range (inclusive)
- * @param end the end of the range (inclusive)
- * @param displayedValues the values displayed to the user.
- */
- public void setRange(int start, int end, String[] displayedValues) {
- mDisplayedValues = displayedValues;
- mStart = start;
- mEnd = end;
- mCurrent = start;
- updateView();
- }
-
- public void setCurrent(int current) {
- mCurrent = current;
- updateView();
- }
-
- /**
- * The speed (in milliseconds) at which the numbers will scroll
- * when the the +/- buttons are longpressed. Default is 300ms.
- */
- public void setSpeed(long speed) {
- mSpeed = speed;
- }
-
- public void onClick(View v) {
- validateInput(mText);
- if (!mText.hasFocus()) mText.requestFocus();
-
- // now perform the increment/decrement
- if (R.id.increment == v.getId()) {
- changeCurrent(mCurrent + 1);
- } else if (R.id.decrement == v.getId()) {
- changeCurrent(mCurrent - 1);
- }
- }
-
- private String formatNumber(int value) {
- return (mFormatter != null)
- ? mFormatter.toString(value)
- : String.valueOf(value);
- }
-
- protected void changeCurrent(int current) {
-
- // Wrap around the values if we go past the start or end
- if (current > mEnd) {
- current = mStart;
- } else if (current < mStart) {
- current = mEnd;
- }
- mPrevious = mCurrent;
- mCurrent = current;
- notifyChange();
- updateView();
- }
-
- protected void notifyChange() {
- if (mListener != null) {
- mListener.onChanged(this, mPrevious, mCurrent);
- }
- }
-
- protected void updateView() {
-
- /* If we don't have displayed values then use the
- * current number else find the correct value in the
- * displayed values for the current number.
- */
- if (mDisplayedValues == null) {
- mText.setText(formatNumber(mCurrent));
- } else {
- mText.setText(mDisplayedValues[mCurrent - mStart]);
- }
- mText.setSelection(mText.getText().length());
- }
-
- private void validateCurrentView(CharSequence str) {
- int val = getSelectedPos(str.toString());
- if ((val >= mStart) && (val <= mEnd)) {
- if (mCurrent != val) {
- mPrevious = mCurrent;
- mCurrent = val;
- notifyChange();
- }
- }
- updateView();
- }
-
- public void onFocusChange(View v, boolean hasFocus) {
-
- /* When focus is lost check that the text field
- * has valid values.
- */
- if (!hasFocus) {
- validateInput(v);
- }
- }
-
- private void validateInput(View v) {
- String str = String.valueOf(((TextView) v).getText());
- if ("".equals(str)) {
-
- // Restore to the old value as we don't allow empty values
- updateView();
- } else {
-
- // Check the new value and ensure it's in range
- validateCurrentView(str);
- }
- }
-
- /**
- * We start the long click here but rely on the {@link NumberPickerButton}
- * to inform us when the long click has ended.
- */
- public boolean onLongClick(View v) {
-
- /* The text view may still have focus so clear it's focus which will
- * trigger the on focus changed and any typed values to be pulled.
- */
- mText.clearFocus();
-
- if (R.id.increment == v.getId()) {
- mIncrement = true;
- mHandler.post(mRunnable);
- } else if (R.id.decrement == v.getId()) {
- mDecrement = true;
- mHandler.post(mRunnable);
- }
- return true;
- }
-
- public void cancelIncrement() {
- mIncrement = false;
- }
-
- public void cancelDecrement() {
- mDecrement = false;
- }
-
- private static final char[] DIGIT_CHARACTERS = new char[] {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
- };
-
- private NumberPickerButton mIncrementButton;
- private NumberPickerButton mDecrementButton;
-
- private class NumberPickerInputFilter implements InputFilter {
- public CharSequence filter(CharSequence source, int start, int end,
- Spanned dest, int dstart, int dend) {
- if (mDisplayedValues == null) {
- return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
- }
- CharSequence filtered = String.valueOf(source.subSequence(start, end));
- String result = String.valueOf(dest.subSequence(0, dstart))
- + filtered
- + dest.subSequence(dend, dest.length());
- String str = String.valueOf(result).toLowerCase();
- for (String val : mDisplayedValues) {
- val = val.toLowerCase();
- if (val.startsWith(str)) {
- return filtered;
- }
- }
- return "";
- }
- }
-
- private class NumberRangeKeyListener extends NumberKeyListener {
-
- // XXX This doesn't allow for range limits when controlled by a
- // soft input method!
- public int getInputType() {
- return InputType.TYPE_CLASS_NUMBER;
- }
-
- @Override
- protected char[] getAcceptedChars() {
- return DIGIT_CHARACTERS;
- }
-
- @Override
- public CharSequence filter(CharSequence source, int start, int end,
- Spanned dest, int dstart, int dend) {
-
- CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
- if (filtered == null) {
- filtered = source.subSequence(start, end);
- }
-
- String result = String.valueOf(dest.subSequence(0, dstart))
- + filtered
- + dest.subSequence(dend, dest.length());
-
- if ("".equals(result)) {
- return result;
- }
- int val = getSelectedPos(result);
-
- /* Ensure the user can't type in a value greater
- * than the max allowed. We have to allow less than min
- * as the user might want to delete some numbers
- * and then type a new number.
- */
- if (val > mEnd) {
- return "";
- } else {
- return filtered;
- }
- }
- }
-
- private int getSelectedPos(String str) {
- if (mDisplayedValues == null) {
- return Integer.parseInt(str);
- } else {
- for (int i = 0; i < mDisplayedValues.length; i++) {
-
- /* Don't force the user to type in jan when ja will do */
- str = str.toLowerCase();
- if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
- return mStart + i;
- }
- }
-
- /* The user might have typed in a number into the month field i.e.
- * 10 instead of OCT so support that too.
- */
- try {
- return Integer.parseInt(str);
- } catch (NumberFormatException e) {
-
- /* Ignore as if it's not a number we don't care */
- }
- }
- return mStart;
- }
-
- /**
- * @return the current value.
- */
- public int getCurrent() {
- return mCurrent;
- }
-} \ No newline at end of file
diff --git a/core/java/com/android/internal/widget/NumberPickerButton.java b/core/java/com/android/internal/widget/NumberPickerButton.java
deleted file mode 100644
index 39f1e2c..0000000
--- a/core/java/com/android/internal/widget/NumberPickerButton.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.internal.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.widget.ImageButton;
-
-import com.android.internal.R;
-
-/**
- * This class exists purely to cancel long click events.
- */
-public class NumberPickerButton extends ImageButton {
-
- private NumberPicker mNumberPicker;
-
- public NumberPickerButton(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public NumberPickerButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public NumberPickerButton(Context context) {
- super(context);
- }
-
- public void setNumberPicker(NumberPicker picker) {
- mNumberPicker = picker;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- cancelLongpressIfRequired(event);
- return super.onTouchEvent(event);
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- cancelLongpressIfRequired(event);
- return super.onTrackballEvent(event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
- || (keyCode == KeyEvent.KEYCODE_ENTER)) {
- cancelLongpress();
- }
- return super.onKeyUp(keyCode, event);
- }
-
- private void cancelLongpressIfRequired(MotionEvent event) {
- if ((event.getAction() == MotionEvent.ACTION_CANCEL)
- || (event.getAction() == MotionEvent.ACTION_UP)) {
- cancelLongpress();
- }
- }
-
- private void cancelLongpress() {
- if (R.id.increment == getId()) {
- mNumberPicker.cancelIncrement();
- } else if (R.id.decrement == getId()) {
- mNumberPicker.cancelDecrement();
- }
- }
-}
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index f07b2f1..adafbb4 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -23,8 +23,6 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.Log;
@@ -34,7 +32,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;
@@ -214,7 +211,7 @@ public class SlidingTab extends ViewGroup {
// Create hint TextView
text = new TextView(parent.getContext());
text.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.FILL_PARENT));
+ LayoutParams.MATCH_PARENT));
text.setBackgroundResource(barId);
text.setTextAppearance(parent.getContext(), R.style.TextAppearance_SlidingTabNormal);
// hint.setSingleLine(); // Hmm.. this causes the text to disappear off-screen
diff --git a/core/java/com/android/internal/widget/VerticalTextSpinner.java b/core/java/com/android/internal/widget/VerticalTextSpinner.java
deleted file mode 100644
index 50c528c..0000000
--- a/core/java/com/android/internal/widget/VerticalTextSpinner.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.internal.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.text.TextPaint;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-
-
-public class VerticalTextSpinner extends View {
-
- private static final int SELECTOR_ARROW_HEIGHT = 15;
-
- private static final int TEXT_SPACING = 18;
- private static final int TEXT_MARGIN_RIGHT = 25;
- private static final int TEXT_SIZE = 22;
-
- /* Keep the calculations as this is really a for loop from
- * -2 to 2 but precalculated so we don't have to do in the onDraw.
- */
- private static final int TEXT1_Y = (TEXT_SIZE * (-2 + 2)) + (TEXT_SPACING * (-2 + 1));
- private static final int TEXT2_Y = (TEXT_SIZE * (-1 + 2)) + (TEXT_SPACING * (-1 + 1));
- private static final int TEXT3_Y = (TEXT_SIZE * (0 + 2)) + (TEXT_SPACING * (0 + 1));
- private static final int TEXT4_Y = (TEXT_SIZE * (1 + 2)) + (TEXT_SPACING * (1 + 1));
- private static final int TEXT5_Y = (TEXT_SIZE * (2 + 2)) + (TEXT_SPACING * (2 + 1));
-
- private static final int SCROLL_MODE_NONE = 0;
- private static final int SCROLL_MODE_UP = 1;
- private static final int SCROLL_MODE_DOWN = 2;
-
- private static final long DEFAULT_SCROLL_INTERVAL_MS = 400;
- private static final int SCROLL_DISTANCE = TEXT_SIZE + TEXT_SPACING;
- private static final int MIN_ANIMATIONS = 4;
-
- private final Drawable mBackgroundFocused;
- private final Drawable mSelectorFocused;
- private final Drawable mSelectorNormal;
- private final int mSelectorDefaultY;
- private final int mSelectorMinY;
- private final int mSelectorMaxY;
- private final int mSelectorHeight;
- private final TextPaint mTextPaintDark;
- private final TextPaint mTextPaintLight;
-
- private int mSelectorY;
- private Drawable mSelector;
- private int mDownY;
- private boolean isDraggingSelector;
- private int mScrollMode;
- private long mScrollInterval;
- private boolean mIsAnimationRunning;
- private boolean mStopAnimation;
- private boolean mWrapAround = true;
-
- private int mTotalAnimatedDistance;
- private int mNumberOfAnimations;
- private long mDelayBetweenAnimations;
- private int mDistanceOfEachAnimation;
-
- private String[] mTextList;
- private int mCurrentSelectedPos;
- private OnChangedListener mListener;
-
- private String mText1;
- private String mText2;
- private String mText3;
- private String mText4;
- private String mText5;
-
- public interface OnChangedListener {
- void onChanged(
- VerticalTextSpinner spinner, int oldPos, int newPos, String[] items);
- }
-
- public VerticalTextSpinner(Context context) {
- this(context, null);
- }
-
- public VerticalTextSpinner(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public VerticalTextSpinner(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
-
- mBackgroundFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_background);
- mSelectorFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_selected);
- mSelectorNormal = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_unselected);
-
- mSelectorHeight = mSelectorFocused.getIntrinsicHeight();
- mSelectorDefaultY = (mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight) / 2;
- mSelectorMinY = 0;
- mSelectorMaxY = mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight;
-
- mSelector = mSelectorNormal;
- mSelectorY = mSelectorDefaultY;
-
- mTextPaintDark = new TextPaint(Paint.ANTI_ALIAS_FLAG);
- mTextPaintDark.setTextSize(TEXT_SIZE);
- mTextPaintDark.setColor(context.getResources().getColor(com.android.internal.R.color.primary_text_light));
-
- mTextPaintLight = new TextPaint(Paint.ANTI_ALIAS_FLAG);
- mTextPaintLight.setTextSize(TEXT_SIZE);
- mTextPaintLight.setColor(context.getResources().getColor(com.android.internal.R.color.secondary_text_dark));
-
- mScrollMode = SCROLL_MODE_NONE;
- mScrollInterval = DEFAULT_SCROLL_INTERVAL_MS;
- calculateAnimationValues();
- }
-
- public void setOnChangeListener(OnChangedListener listener) {
- mListener = listener;
- }
-
- public void setItems(String[] textList) {
- mTextList = textList;
- calculateTextPositions();
- }
-
- public void setSelectedPos(int selectedPos) {
- mCurrentSelectedPos = selectedPos;
- calculateTextPositions();
- postInvalidate();
- }
-
- public void setScrollInterval(long interval) {
- mScrollInterval = interval;
- calculateAnimationValues();
- }
-
- public void setWrapAround(boolean wrap) {
- mWrapAround = wrap;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
-
- /* This is a bit confusing, when we get the key event
- * DPAD_DOWN we actually roll the spinner up. When the
- * key event is DPAD_UP we roll the spinner down.
- */
- if ((keyCode == KeyEvent.KEYCODE_DPAD_UP) && canScrollDown()) {
- mScrollMode = SCROLL_MODE_DOWN;
- scroll();
- mStopAnimation = true;
- return true;
- } else if ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && canScrollUp()) {
- mScrollMode = SCROLL_MODE_UP;
- scroll();
- mStopAnimation = true;
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- private boolean canScrollDown() {
- return (mCurrentSelectedPos > 0) || mWrapAround;
- }
-
- private boolean canScrollUp() {
- return ((mCurrentSelectedPos < (mTextList.length - 1)) || mWrapAround);
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction,
- Rect previouslyFocusedRect) {
- if (gainFocus) {
- setBackgroundDrawable(mBackgroundFocused);
- mSelector = mSelectorFocused;
- } else {
- setBackgroundDrawable(null);
- mSelector = mSelectorNormal;
- mSelectorY = mSelectorDefaultY;
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
- final int y = (int) event.getY();
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- requestFocus();
- mDownY = y;
- isDraggingSelector = (y >= mSelectorY) && (y <= (mSelectorY + mSelector.getIntrinsicHeight()));
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (isDraggingSelector) {
- int top = mSelectorDefaultY + (y - mDownY);
- if (top <= mSelectorMinY && canScrollDown()) {
- mSelectorY = mSelectorMinY;
- mStopAnimation = false;
- if (mScrollMode != SCROLL_MODE_DOWN) {
- mScrollMode = SCROLL_MODE_DOWN;
- scroll();
- }
- } else if (top >= mSelectorMaxY && canScrollUp()) {
- mSelectorY = mSelectorMaxY;
- mStopAnimation = false;
- if (mScrollMode != SCROLL_MODE_UP) {
- mScrollMode = SCROLL_MODE_UP;
- scroll();
- }
- } else {
- mSelectorY = top;
- mStopAnimation = true;
- }
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- default:
- mSelectorY = mSelectorDefaultY;
- mStopAnimation = true;
- invalidate();
- break;
- }
- return true;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
-
- /* The bounds of the selector */
- final int selectorLeft = 0;
- final int selectorTop = mSelectorY;
- final int selectorRight = mMeasuredWidth;
- final int selectorBottom = mSelectorY + mSelectorHeight;
-
- /* Draw the selector */
- mSelector.setBounds(selectorLeft, selectorTop, selectorRight, selectorBottom);
- mSelector.draw(canvas);
-
- if (mTextList == null) {
-
- /* We're not setup with values so don't draw anything else */
- return;
- }
-
- final TextPaint textPaintDark = mTextPaintDark;
- if (hasFocus()) {
-
- /* The bounds of the top area where the text should be light */
- final int topLeft = 0;
- final int topTop = 0;
- final int topRight = selectorRight;
- final int topBottom = selectorTop + SELECTOR_ARROW_HEIGHT;
-
- /* Assign a bunch of local finals for performance */
- final String text1 = mText1;
- final String text2 = mText2;
- final String text3 = mText3;
- final String text4 = mText4;
- final String text5 = mText5;
- final TextPaint textPaintLight = mTextPaintLight;
-
- /*
- * Draw the 1st, 2nd and 3rd item in light only, clip it so it only
- * draws in the area above the selector
- */
- canvas.save();
- canvas.clipRect(topLeft, topTop, topRight, topBottom);
- drawText(canvas, text1, TEXT1_Y
- + mTotalAnimatedDistance, textPaintLight);
- drawText(canvas, text2, TEXT2_Y
- + mTotalAnimatedDistance, textPaintLight);
- drawText(canvas, text3,
- TEXT3_Y + mTotalAnimatedDistance, textPaintLight);
- canvas.restore();
-
- /*
- * Draw the 2nd, 3rd and 4th clipped to the selector bounds in dark
- * paint
- */
- canvas.save();
- canvas.clipRect(selectorLeft, selectorTop + SELECTOR_ARROW_HEIGHT,
- selectorRight, selectorBottom - SELECTOR_ARROW_HEIGHT);
- drawText(canvas, text2, TEXT2_Y
- + mTotalAnimatedDistance, textPaintDark);
- drawText(canvas, text3,
- TEXT3_Y + mTotalAnimatedDistance, textPaintDark);
- drawText(canvas, text4,
- TEXT4_Y + mTotalAnimatedDistance, textPaintDark);
- canvas.restore();
-
- /* The bounds of the bottom area where the text should be light */
- final int bottomLeft = 0;
- final int bottomTop = selectorBottom - SELECTOR_ARROW_HEIGHT;
- final int bottomRight = selectorRight;
- final int bottomBottom = mMeasuredHeight;
-
- /*
- * Draw the 3rd, 4th and 5th in white text, clip it so it only draws
- * in the area below the selector.
- */
- canvas.save();
- canvas.clipRect(bottomLeft, bottomTop, bottomRight, bottomBottom);
- drawText(canvas, text3,
- TEXT3_Y + mTotalAnimatedDistance, textPaintLight);
- drawText(canvas, text4,
- TEXT4_Y + mTotalAnimatedDistance, textPaintLight);
- drawText(canvas, text5,
- TEXT5_Y + mTotalAnimatedDistance, textPaintLight);
- canvas.restore();
-
- } else {
- drawText(canvas, mText3, TEXT3_Y, textPaintDark);
- }
- if (mIsAnimationRunning) {
- if ((Math.abs(mTotalAnimatedDistance) + mDistanceOfEachAnimation) > SCROLL_DISTANCE) {
- mTotalAnimatedDistance = 0;
- if (mScrollMode == SCROLL_MODE_UP) {
- int oldPos = mCurrentSelectedPos;
- int newPos = getNewIndex(1);
- if (newPos >= 0) {
- mCurrentSelectedPos = newPos;
- if (mListener != null) {
- mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList);
- }
- }
- if (newPos < 0 || ((newPos >= mTextList.length - 1) && !mWrapAround)) {
- mStopAnimation = true;
- }
- calculateTextPositions();
- } else if (mScrollMode == SCROLL_MODE_DOWN) {
- int oldPos = mCurrentSelectedPos;
- int newPos = getNewIndex(-1);
- if (newPos >= 0) {
- mCurrentSelectedPos = newPos;
- if (mListener != null) {
- mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList);
- }
- }
- if (newPos < 0 || (newPos == 0 && !mWrapAround)) {
- mStopAnimation = true;
- }
- calculateTextPositions();
- }
- if (mStopAnimation) {
- final int previousScrollMode = mScrollMode;
-
- /* No longer scrolling, we wait till the current animation
- * completes then we stop.
- */
- mIsAnimationRunning = false;
- mStopAnimation = false;
- mScrollMode = SCROLL_MODE_NONE;
-
- /* If the current selected item is an empty string
- * scroll past it.
- */
- if ("".equals(mTextList[mCurrentSelectedPos])) {
- mScrollMode = previousScrollMode;
- scroll();
- mStopAnimation = true;
- }
- }
- } else {
- if (mScrollMode == SCROLL_MODE_UP) {
- mTotalAnimatedDistance -= mDistanceOfEachAnimation;
- } else if (mScrollMode == SCROLL_MODE_DOWN) {
- mTotalAnimatedDistance += mDistanceOfEachAnimation;
- }
- }
- if (mDelayBetweenAnimations > 0) {
- postInvalidateDelayed(mDelayBetweenAnimations);
- } else {
- invalidate();
- }
- }
- }
-
- /**
- * Called every time the text items or current position
- * changes. We calculate store we don't have to calculate
- * onDraw.
- */
- private void calculateTextPositions() {
- mText1 = getTextToDraw(-2);
- mText2 = getTextToDraw(-1);
- mText3 = getTextToDraw(0);
- mText4 = getTextToDraw(1);
- mText5 = getTextToDraw(2);
- }
-
- private String getTextToDraw(int offset) {
- int index = getNewIndex(offset);
- if (index < 0) {
- return "";
- }
- return mTextList[index];
- }
-
- private int getNewIndex(int offset) {
- int index = mCurrentSelectedPos + offset;
- if (index < 0) {
- if (mWrapAround) {
- index += mTextList.length;
- } else {
- return -1;
- }
- } else if (index >= mTextList.length) {
- if (mWrapAround) {
- index -= mTextList.length;
- } else {
- return -1;
- }
- }
- return index;
- }
-
- private void scroll() {
- if (mIsAnimationRunning) {
- return;
- }
- mTotalAnimatedDistance = 0;
- mIsAnimationRunning = true;
- invalidate();
- }
-
- private void calculateAnimationValues() {
- mNumberOfAnimations = (int) mScrollInterval / SCROLL_DISTANCE;
- if (mNumberOfAnimations < MIN_ANIMATIONS) {
- mNumberOfAnimations = MIN_ANIMATIONS;
- mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations;
- mDelayBetweenAnimations = 0;
- } else {
- mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations;
- mDelayBetweenAnimations = mScrollInterval / mNumberOfAnimations;
- }
- }
-
- private void drawText(Canvas canvas, String text, int y, TextPaint paint) {
- int width = (int) paint.measureText(text);
- int x = getMeasuredWidth() - width - TEXT_MARGIN_RIGHT;
- canvas.drawText(text, x, y, paint);
- }
-
- public int getCurrentSelectedPos() {
- return mCurrentSelectedPos;
- }
-}
diff --git a/core/java/com/google/android/collect/Sets.java b/core/java/com/google/android/collect/Sets.java
index f5be0ec..fbfbe50 100644
--- a/core/java/com/google/android/collect/Sets.java
+++ b/core/java/com/google/android/collect/Sets.java
@@ -44,41 +44,50 @@ public class Sets {
return new HashSet<K>();
}
- /**
- * Creates a {@code HashSet} instance containing the given elements.
- *
- * <p><b>Note:</b> due to a bug in javac 1.5.0_06, we cannot support the
- * following:
- *
- * <p>{@code Set<Base> set = Sets.newHashSet(sub1, sub2);}
- *
- * <p>where {@code sub1} and {@code sub2} are references to subtypes of {@code
- * Base}, not of {@code Base} itself. To get around this, you must use:
- *
- * <p>{@code Set<Base> set = Sets.<Base>newHashSet(sub1, sub2);}
- *
- * @param elements the elements that the set should contain
- * @return a newly-created {@code HashSet} containing those elements (minus
- * duplicates)
- */
- public static <E> HashSet<E> newHashSet(E... elements) {
- int capacity = elements.length * 4 / 3 + 1;
- HashSet<E> set = new HashSet<E>(capacity);
- Collections.addAll(set, elements);
- return set;
- }
+ /**
+ * Creates a {@code HashSet} instance containing the given elements.
+ *
+ * <p><b>Note:</b> due to a bug in javac 1.5.0_06, we cannot support the
+ * following:
+ *
+ * <p>{@code Set<Base> set = Sets.newHashSet(sub1, sub2);}
+ *
+ * <p>where {@code sub1} and {@code sub2} are references to subtypes of {@code
+ * Base}, not of {@code Base} itself. To get around this, you must use:
+ *
+ * <p>{@code Set<Base> set = Sets.<Base>newHashSet(sub1, sub2);}
+ *
+ * @param elements the elements that the set should contain
+ * @return a newly-created {@code HashSet} containing those elements (minus
+ * duplicates)
+ */
+ public static <E> HashSet<E> newHashSet(E... elements) {
+ int capacity = elements.length * 4 / 3 + 1;
+ HashSet<E> set = new HashSet<E>(capacity);
+ Collections.addAll(set, elements);
+ return set;
+ }
- /**
- * Creates a {@code SortedSet} instance containing the given elements.
- *
- * @param elements the elements that the set should contain
- * @return a newly-created {@code SortedSet} containing those elements (minus
- * duplicates)
- */
- public static <E> SortedSet<E> newSortedSet(E... elements) {
- SortedSet<E> set = new TreeSet<E>();
- Collections.addAll(set, elements);
- return set;
- }
+ /**
+ * Creates an empty {@code SortedSet} instance.
+ *
+ * @return a newly-created, initially-empty {@code SortedSet}.
+ */
+ public static <E> SortedSet<E> newSortedSet() {
+ return new TreeSet<E>();
+ }
+
+ /**
+ * Creates a {@code SortedSet} instance containing the given elements.
+ *
+ * @param elements the elements that the set should contain
+ * @return a newly-created {@code SortedSet} containing those elements (minus
+ * duplicates)
+ */
+ public static <E> SortedSet<E> newSortedSet(E... elements) {
+ SortedSet<E> set = new TreeSet<E>();
+ Collections.addAll(set, elements);
+ return set;
+ }
}
diff --git a/core/java/com/google/android/gdata/client/AndroidGDataClient.java b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
deleted file mode 100644
index 9a2a51d..0000000
--- a/core/java/com/google/android/gdata/client/AndroidGDataClient.java
+++ /dev/null
@@ -1,508 +0,0 @@
-// Copyright 2007 The Android Open Source Project
-
-package com.google.android.gdata.client;
-
-import com.google.android.net.GoogleHttpClient;
-import com.google.wireless.gdata.client.GDataClient;
-import com.google.wireless.gdata.client.HttpException;
-import com.google.wireless.gdata.client.QueryParams;
-import com.google.wireless.gdata.data.StringUtils;
-import com.google.wireless.gdata.parser.ParseException;
-import com.google.wireless.gdata.serializer.GDataSerializer;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.entity.InputStreamEntity;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.http.AndroidHttpClient;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.Log;
-import android.os.SystemProperties;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.io.BufferedInputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-
-/**
- * Implementation of a GDataClient using GoogleHttpClient to make HTTP
- * requests. Always issues GETs and POSTs, using the X-HTTP-Method-Override
- * header when a PUT or DELETE is desired, to avoid issues with firewalls, etc.,
- * that do not allow methods other than GET or POST.
- */
-public class AndroidGDataClient implements GDataClient {
-
- private static final String TAG = "GDataClient";
- private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
-
- private static final String X_HTTP_METHOD_OVERRIDE =
- "X-HTTP-Method-Override";
-
- private static final String DEFAULT_USER_AGENT_APP_VERSION = "Android-GData/1.1";
-
- private static final int MAX_REDIRECTS = 10;
-
- // boolean system property that can be used to control whether or not
- // requests/responses are gzip'd.
- private static final String NO_GZIP_SYSTEM_PROPERTY = "sync.nogzip";
-
- private final GoogleHttpClient mHttpClient;
- private ContentResolver mResolver;
-
- /**
- * Interface for creating HTTP requests. Used by
- * {@link AndroidGDataClient#createAndExecuteMethod}, since HttpUriRequest does not allow for
- * changing the URI after creation, e.g., when you want to follow a redirect.
- */
- private interface HttpRequestCreator {
- HttpUriRequest createRequest(URI uri);
- }
-
- private static class GetRequestCreator implements HttpRequestCreator {
- public GetRequestCreator() {
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpGet get = new HttpGet(uri);
- return get;
- }
- }
-
- private static class PostRequestCreator implements HttpRequestCreator {
- private final String mMethodOverride;
- private final HttpEntity mEntity;
- public PostRequestCreator(String methodOverride, HttpEntity entity) {
- mMethodOverride = methodOverride;
- mEntity = entity;
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpPost post = new HttpPost(uri);
- if (mMethodOverride != null) {
- post.addHeader(X_HTTP_METHOD_OVERRIDE, mMethodOverride);
- }
- post.setEntity(mEntity);
- return post;
- }
- }
-
- // MAJOR TODO: make this work across redirects (if we can reset the InputStream).
- // OR, read the bits into a local buffer (yuck, the media could be large).
- private static class MediaPutRequestCreator implements HttpRequestCreator {
- private final InputStream mMediaInputStream;
- private final String mContentType;
- public MediaPutRequestCreator(InputStream mediaInputStream, String contentType) {
- mMediaInputStream = mediaInputStream;
- mContentType = contentType;
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpPost post = new HttpPost(uri);
- post.addHeader(X_HTTP_METHOD_OVERRIDE, "PUT");
- // mMediaInputStream.reset();
- InputStreamEntity entity = new InputStreamEntity(mMediaInputStream,
- -1 /* read until EOF */);
- entity.setContentType(mContentType);
- post.setEntity(entity);
- return post;
- }
- }
-
- /**
- * @deprecated Use AndroidGDAtaClient(Context) instead.
- */
- public AndroidGDataClient(ContentResolver resolver) {
- mHttpClient = new GoogleHttpClient(resolver, DEFAULT_USER_AGENT_APP_VERSION,
- true /* gzip capable */);
- mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
- mResolver = resolver;
- }
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- */
- public AndroidGDataClient(Context context) {
- this(context, DEFAULT_USER_AGENT_APP_VERSION);
- }
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- * @param appAndVersion The application name and version to be used as the basis of the
- * User-Agent. e.g., Android-GData/1.5.0.
- */
- public AndroidGDataClient(Context context, String appAndVersion) {
- mHttpClient = new GoogleHttpClient(context, appAndVersion,
- true /* gzip capable */);
- mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
- mResolver = context.getContentResolver();
- }
-
- public void close() {
- mHttpClient.close();
- }
-
- /*
- * (non-Javadoc)
- * @see GDataClient#encodeUri(java.lang.String)
- */
- public String encodeUri(String uri) {
- String encodedUri;
- try {
- encodedUri = URLEncoder.encode(uri, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- // should not happen.
- Log.e("JakartaGDataClient",
- "UTF-8 not supported -- should not happen. "
- + "Using default encoding.", uee);
- encodedUri = URLEncoder.encode(uri);
- }
- return encodedUri;
- }
-
- /*
- * (non-Javadoc)
- * @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
- */
- public QueryParams createQueryParams() {
- return new QueryParamsImpl();
- }
-
- // follows redirects
- private InputStream createAndExecuteMethod(HttpRequestCreator creator,
- String uriString,
- String authToken)
- throws HttpException, IOException {
-
- HttpResponse response = null;
- int status = 500;
- int redirectsLeft = MAX_REDIRECTS;
-
- URI uri;
- try {
- uri = new URI(uriString);
- } catch (URISyntaxException use) {
- Log.w(TAG, "Unable to parse " + uriString + " as URI.", use);
- throw new IOException("Unable to parse " + uriString + " as URI: "
- + use.getMessage());
- }
-
- // we follow redirects ourselves, since we want to follow redirects even on POSTs, which
- // the HTTP library does not do. following redirects ourselves also allows us to log
- // the redirects using our own logging.
- while (redirectsLeft > 0) {
-
- HttpUriRequest request = creator.createRequest(uri);
-
- if (!SystemProperties.getBoolean(NO_GZIP_SYSTEM_PROPERTY, false)) {
- AndroidHttpClient.modifyRequestToAcceptGzipResponse(request);
- }
-
- // only add the auth token if not null (to allow for GData feeds that do not require
- // authentication.)
- if (!TextUtils.isEmpty(authToken)) {
- request.addHeader("Authorization", "GoogleLogin auth=" + authToken);
- }
- if (LOCAL_LOGV) {
- for (Header h : request.getAllHeaders()) {
- Log.v(TAG, h.getName() + ": " + h.getValue());
- }
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Executing " + request.getRequestLine().toString());
- }
-
- response = null;
-
- try {
- response = mHttpClient.execute(request);
- } catch (IOException ioe) {
- Log.w(TAG, "Unable to execute HTTP request." + ioe);
- throw ioe;
- }
-
- StatusLine statusLine = response.getStatusLine();
- if (statusLine == null) {
- Log.w(TAG, "StatusLine is null.");
- throw new NullPointerException("StatusLine is null -- should not happen.");
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, response.getStatusLine().toString());
- for (Header h : response.getAllHeaders()) {
- Log.d(TAG, h.getName() + ": " + h.getValue());
- }
- }
- status = statusLine.getStatusCode();
-
- HttpEntity entity = response.getEntity();
-
- if ((status >= 200) && (status < 300) && entity != null) {
- InputStream in = AndroidHttpClient.getUngzippedContent(entity);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- in = logInputStreamContents(in);
- }
- return in;
- }
-
- // TODO: handle 301, 307?
- // TODO: let the http client handle the redirects, if we can be sure we'll never get a
- // redirect on POST.
- if (status == 302) {
- // consume the content, so the connection can be closed.
- entity.consumeContent();
- Header location = response.getFirstHeader("Location");
- if (location == null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Redirect requested but no Location "
- + "specified.");
- }
- break;
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Following redirect to " + location.getValue());
- }
- try {
- uri = new URI(location.getValue());
- } catch (URISyntaxException use) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Unable to parse " + location.getValue() + " as URI.", use);
- throw new IOException("Unable to parse " + location.getValue()
- + " as URI.");
- }
- break;
- }
- --redirectsLeft;
- } else {
- break;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Received " + status + " status code.");
- }
- String errorMessage = null;
- HttpEntity entity = response.getEntity();
- try {
- if (response != null && entity != null) {
- InputStream in = AndroidHttpClient.getUngzippedContent(entity);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buf = new byte[8192];
- int bytesRead = -1;
- while ((bytesRead = in.read(buf)) != -1) {
- baos.write(buf, 0, bytesRead);
- }
- // TODO: use appropriate encoding, picked up from Content-Type.
- errorMessage = new String(baos.toByteArray());
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, errorMessage);
- }
- }
- } finally {
- if (entity != null) {
- entity.consumeContent();
- }
- }
- String exceptionMessage = "Received " + status + " status code";
- if (errorMessage != null) {
- exceptionMessage += (": " + errorMessage);
- }
- throw new HttpException(exceptionMessage, status, null /* InputStream */);
- }
-
- /*
- * (non-Javadoc)
- * @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
- */
- public InputStream getFeedAsStream(String feedUrl,
- String authToken)
- throws HttpException, IOException {
-
- InputStream in = createAndExecuteMethod(new GetRequestCreator(), feedUrl, authToken);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to access feed.");
- }
-
- /**
- * Log the contents of the input stream.
- * The original input stream is consumed, so the caller must use the
- * BufferedInputStream that is returned.
- * @param in InputStream
- * @return replacement input stream for caller to use
- * @throws IOException
- */
- private InputStream logInputStreamContents(InputStream in) throws IOException {
- if (in == null) {
- return in;
- }
- // bufferSize is the (arbitrary) maximum amount to log.
- // The original InputStream is wrapped in a
- // BufferedInputStream with a 16K buffer. This lets
- // us read up to 16K, write it to the log, and then
- // reset the stream so the the original client can
- // then read the data. The BufferedInputStream
- // provides the mark and reset support, even when
- // the original InputStream does not.
- final int bufferSize = 16384;
- BufferedInputStream bin = new BufferedInputStream(in, bufferSize);
- bin.mark(bufferSize);
- int wanted = bufferSize;
- int totalReceived = 0;
- byte buf[] = new byte[wanted];
- while (wanted > 0) {
- int got = bin.read(buf, totalReceived, wanted);
- if (got <= 0) break; // EOF
- wanted -= got;
- totalReceived += got;
- }
- Log.d(TAG, new String(buf, 0, totalReceived, "UTF-8"));
- bin.reset();
- return bin;
- }
-
- public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken)
- throws HttpException, IOException {
-
- InputStream in = createAndExecuteMethod(new GetRequestCreator(), mediaEntryUrl, authToken);
-
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to access media entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#createEntry
- */
- public InputStream createEntry(String feedUrl,
- String authToken,
- GDataSerializer entry)
- throws HttpException, IOException {
-
- HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_CREATE);
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator(null /* override */, entity),
- feedUrl,
- authToken);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to create entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#updateEntry
- */
- public InputStream updateEntry(String editUri,
- String authToken,
- GDataSerializer entry)
- throws HttpException, IOException {
- HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_UPDATE);
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator("PUT", entity),
- editUri,
- authToken);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to update entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#deleteEntry
- */
- public void deleteEntry(String editUri, String authToken)
- throws HttpException, IOException {
- if (StringUtils.isEmpty(editUri)) {
- throw new IllegalArgumentException(
- "you must specify an non-empty edit url");
- }
- InputStream in =
- createAndExecuteMethod(
- new PostRequestCreator("DELETE", null /* entity */),
- editUri,
- authToken);
- if (in == null) {
- throw new IOException("Unable to delete entry.");
- }
- try {
- in.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
-
- public InputStream updateMediaEntry(String editUri, String authToken,
- InputStream mediaEntryInputStream, String contentType)
- throws HttpException, IOException {
- InputStream in = createAndExecuteMethod(
- new MediaPutRequestCreator(mediaEntryInputStream, contentType),
- editUri,
- authToken);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to write media entry.");
- }
-
- private HttpEntity createEntityForEntry(GDataSerializer entry, int format) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- entry.serialize(baos, format);
- } catch (IOException ioe) {
- Log.e(TAG, "Unable to serialize entry.", ioe);
- throw ioe;
- } catch (ParseException pe) {
- Log.e(TAG, "Unable to serialize entry.", pe);
- throw new IOException("Unable to serialize entry: " + pe.getMessage());
- }
-
- byte[] entryBytes = baos.toByteArray();
-
- if (entryBytes != null && Log.isLoggable(TAG, Log.DEBUG)) {
- try {
- Log.d(TAG, "Serialized entry: " + new String(entryBytes, "UTF-8"));
- } catch (UnsupportedEncodingException uee) {
- // should not happen
- throw new IllegalStateException("UTF-8 should be supported!",
- uee);
- }
- }
-
- AbstractHttpEntity entity;
- if (SystemProperties.getBoolean(NO_GZIP_SYSTEM_PROPERTY, false)) {
- entity = new ByteArrayEntity(entryBytes);
- } else {
- entity = AndroidHttpClient.getCompressedEntity(entryBytes, mResolver);
- }
- entity.setContentType(entry.getContentType());
- return entity;
- }
-}
diff --git a/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java b/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java
deleted file mode 100644
index a308fc0..0000000
--- a/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.google.android.gdata.client;
-
-import com.google.wireless.gdata.parser.xml.XmlParserFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Xml;
-
-/**
- * XmlParserFactory for the Android platform.
- */
-public class AndroidXmlParserFactory implements XmlParserFactory {
-
- /*
- * (non-javadoc)
- * @see XmlParserFactory#createParser
- */
- public XmlPullParser createParser() throws XmlPullParserException {
- return Xml.newPullParser();
- }
-
- /*
- * (non-javadoc)
- * @see XmlParserFactory#createSerializer
- */
- public XmlSerializer createSerializer() throws XmlPullParserException {
- return Xml.newSerializer();
- }
-}
diff --git a/core/java/com/google/android/gdata/client/QueryParamsImpl.java b/core/java/com/google/android/gdata/client/QueryParamsImpl.java
deleted file mode 100644
index fbe0d22..0000000
--- a/core/java/com/google/android/gdata/client/QueryParamsImpl.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.google.android.gdata.client;
-
-import com.google.wireless.gdata.client.QueryParams;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Simple implementation of the QueryParams interface.
- */
-// TODO: deal with categories
-public class QueryParamsImpl extends QueryParams {
-
- private final Map<String,String> mParams = new HashMap<String,String>();
-
- /**
- * Creates a new empty QueryParamsImpl.
- */
- public QueryParamsImpl() {
- }
-
- @Override
- public void clear() {
- setEntryId(null);
- mParams.clear();
- }
-
- @Override
- public String generateQueryUrl(String feedUrl) {
-
- if (TextUtils.isEmpty(getEntryId()) &&
- mParams.isEmpty()) {
- // nothing to do
- return feedUrl;
- }
-
- // handle entry IDs
- if (!TextUtils.isEmpty(getEntryId())) {
- if (!mParams.isEmpty()) {
- throw new IllegalStateException("Cannot set both an entry ID "
- + "and other query paramters.");
- }
- return feedUrl + '/' + getEntryId();
- }
-
- // otherwise, append the querystring params.
- StringBuilder sb = new StringBuilder();
- sb.append(feedUrl);
- Set<String> params = mParams.keySet();
- boolean first = true;
- if (feedUrl.contains("?")) {
- first = false;
- } else {
- sb.append('?');
- }
- for (String param : params) {
- String value = mParams.get(param);
- if (value == null) continue;
- if (first) {
- first = false;
- } else {
- sb.append('&');
- }
- sb.append(param);
- sb.append('=');
-
- String encodedValue = null;
-
- try {
- encodedValue = URLEncoder.encode(value, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- // should not happen.
- Log.w("QueryParamsImpl",
- "UTF-8 not supported -- should not happen. "
- + "Using default encoding.", uee);
- encodedValue = URLEncoder.encode(value);
- }
- sb.append(encodedValue);
- }
- return sb.toString();
- }
-
- @Override
- public String getParamValue(String param) {
- if (!(mParams.containsKey(param))) {
- return null;
- }
- return mParams.get(param);
- }
-
- @Override
- public void setParamValue(String param, String value) {
- mParams.put(param, value);
- }
-
-}
diff --git a/core/java/com/google/android/gdata2/client/AndroidGDataClient.java b/core/java/com/google/android/gdata2/client/AndroidGDataClient.java
deleted file mode 100644
index 3721fa4..0000000
--- a/core/java/com/google/android/gdata2/client/AndroidGDataClient.java
+++ /dev/null
@@ -1,603 +0,0 @@
-// Copyright 2007 The Android Open Source Project
-
-package com.google.android.gdata2.client;
-
-import com.google.android.net.GoogleHttpClient;
-import com.google.wireless.gdata2.client.GDataClient;
-import com.google.wireless.gdata2.client.HttpException;
-import com.google.wireless.gdata2.client.QueryParams;
-import com.google.wireless.gdata2.data.StringUtils;
-import com.google.wireless.gdata2.parser.ParseException;
-import com.google.wireless.gdata2.serializer.GDataSerializer;
-import com.google.android.gdata2.client.QueryParamsImpl;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.entity.InputStreamEntity;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.http.AndroidHttpClient;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.Log;
-import android.os.SystemProperties;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.io.BufferedInputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-
-/**
- * Implementation of a GDataClient using GoogleHttpClient to make HTTP
- * requests. Always issues GETs and POSTs, using the X-HTTP-Method-Override
- * header when a PUT or DELETE is desired, to avoid issues with firewalls, etc.,
- * that do not allow methods other than GET or POST.
- */
-public class AndroidGDataClient implements GDataClient {
-
- private static final String TAG = "GDataClient";
- private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
-
- private static final String X_HTTP_METHOD_OVERRIDE =
- "X-HTTP-Method-Override";
-
- private static final String DEFAULT_USER_AGENT_APP_VERSION = "Android-GData/1.2";
-
- private static final int MAX_REDIRECTS = 10;
- private static String DEFAULT_GDATA_VERSION = "2.0";
-
- // boolean system property that can be used to control whether or not
- // requests/responses are gzip'd.
- private static final String NO_GZIP_SYSTEM_PROPERTY = "sync.nogzip";
-
- private String mGDataVersion;
- private final GoogleHttpClient mHttpClient;
- private ContentResolver mResolver;
-
- /**
- * Interface for creating HTTP requests. Used by
- * {@link AndroidGDataClient#createAndExecuteMethod}, since HttpUriRequest does not allow for
- * changing the URI after creation, e.g., when you want to follow a redirect.
- */
- private interface HttpRequestCreator {
- HttpUriRequest createRequest(URI uri);
- }
-
- private static class GetRequestCreator implements HttpRequestCreator {
- public GetRequestCreator() {
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpGet get = new HttpGet(uri);
- return get;
- }
- }
-
- private static class PostRequestCreator implements HttpRequestCreator {
- private final String mMethodOverride;
- private final HttpEntity mEntity;
- public PostRequestCreator(String methodOverride, HttpEntity entity) {
- mMethodOverride = methodOverride;
- mEntity = entity;
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpPost post = new HttpPost(uri);
- if (mMethodOverride != null) {
- post.addHeader(X_HTTP_METHOD_OVERRIDE, mMethodOverride);
- }
- post.setEntity(mEntity);
- return post;
- }
- }
-
- // MAJOR TODO: make this work across redirects (if we can reset the InputStream).
- // OR, read the bits into a local buffer (yuck, the media could be large).
- private static class MediaPutRequestCreator implements HttpRequestCreator {
- private final InputStream mMediaInputStream;
- private final String mContentType;
- public MediaPutRequestCreator(InputStream mediaInputStream, String contentType) {
- mMediaInputStream = mediaInputStream;
- mContentType = contentType;
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpPost post = new HttpPost(uri);
- post.addHeader(X_HTTP_METHOD_OVERRIDE, "PUT");
- // mMediaInputStream.reset();
- InputStreamEntity entity = new InputStreamEntity(mMediaInputStream,
- -1 /* read until EOF */);
- entity.setContentType(mContentType);
- post.setEntity(entity);
- return post;
- }
- }
-
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- */
- public AndroidGDataClient(Context context) {
- this(context, DEFAULT_USER_AGENT_APP_VERSION);
- }
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- * @param appAndVersion The application name and version to be used as the basis of the
- * User-Agent. e.g., Android-GData/1.5.0.
- */
- public AndroidGDataClient(Context context, String appAndVersion) {
- this(context, appAndVersion, DEFAULT_GDATA_VERSION);
- }
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- * @param appAndVersion The application name and version to be used as the basis of the
- * User-Agent. e.g., Android-GData/1.5.0.
- * @param gdataVersion The gdata service version that should be
- * used, e.g. "2.0"
- *
- */
- public AndroidGDataClient(Context context, String appAndVersion, String gdataVersion) {
- mHttpClient = new GoogleHttpClient(context, appAndVersion,
- true /* gzip capable */);
- mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
- mResolver = context.getContentResolver();
- mGDataVersion = gdataVersion;
- }
-
-
- public void close() {
- mHttpClient.close();
- }
-
- /*
- * (non-Javadoc)
- * @see GDataClient#encodeUri(java.lang.String)
- */
- public String encodeUri(String uri) {
- String encodedUri;
- try {
- encodedUri = URLEncoder.encode(uri, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- // should not happen.
- Log.e("JakartaGDataClient",
- "UTF-8 not supported -- should not happen. "
- + "Using default encoding.", uee);
- encodedUri = URLEncoder.encode(uri);
- }
- return encodedUri;
- }
-
- /*
- * (non-Javadoc)
- * @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
- */
- public QueryParams createQueryParams() {
- return new QueryParamsImpl();
- }
-
- // follows redirects
- private InputStream createAndExecuteMethod(HttpRequestCreator creator,
- String uriString,
- String authToken,
- String eTag,
- String protocolVersion)
- throws HttpException, IOException {
-
- HttpResponse response = null;
- int status = 500;
- int redirectsLeft = MAX_REDIRECTS;
-
- URI uri;
- try {
- uri = new URI(uriString);
- } catch (URISyntaxException use) {
- Log.w(TAG, "Unable to parse " + uriString + " as URI.", use);
- throw new IOException("Unable to parse " + uriString + " as URI: "
- + use.getMessage());
- }
-
- // we follow redirects ourselves, since we want to follow redirects even on POSTs, which
- // the HTTP library does not do. following redirects ourselves also allows us to log
- // the redirects using our own logging.
- while (redirectsLeft > 0) {
-
- HttpUriRequest request = creator.createRequest(uri);
-
- if (!SystemProperties.getBoolean(NO_GZIP_SYSTEM_PROPERTY, false)) {
- AndroidHttpClient.modifyRequestToAcceptGzipResponse(request);
- }
-
- // only add the auth token if not null (to allow for GData feeds that do not require
- // authentication.)
- if (!TextUtils.isEmpty(authToken)) {
- request.addHeader("Authorization", "GoogleLogin auth=" + authToken);
- }
-
- // while by default we have a 2.0 in this variable, it is possible to construct
- // a client that has an empty version field, to work with 1.0 services.
- if (!TextUtils.isEmpty(mGDataVersion)) {
- request.addHeader("GDataVersion", mGDataVersion);
- }
-
- // if we have a passed down eTag value, we need to add several headers
- if (!TextUtils.isEmpty(eTag)) {
- String method = request.getMethod();
- Header overrideMethodHeader = request.getFirstHeader(X_HTTP_METHOD_OVERRIDE);
- if (overrideMethodHeader != null) {
- method = overrideMethodHeader.getValue();
- }
- if ("GET".equals(method)) {
- // add the none match header, if the resource is not changed
- // this request will result in a 304 now.
- request.addHeader("If-None-Match", eTag);
- } else if ("DELETE".equals(method)
- || "PUT".equals(method)) {
- // now we send an if-match, but only if the passed in eTag is a strong eTag
- // as this only makes sense for a strong eTag
- if (!eTag.startsWith("W/")) {
- request.addHeader("If-Match", eTag);
- }
- }
- }
-
- if (LOCAL_LOGV) {
- for (Header h : request.getAllHeaders()) {
- Log.v(TAG, h.getName() + ": " + h.getValue());
- }
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Executing " + request.getRequestLine().toString());
- }
-
- response = null;
-
- try {
- response = mHttpClient.execute(request);
- } catch (IOException ioe) {
- Log.w(TAG, "Unable to execute HTTP request." + ioe);
- throw ioe;
- }
-
- StatusLine statusLine = response.getStatusLine();
- if (statusLine == null) {
- Log.w(TAG, "StatusLine is null.");
- throw new NullPointerException("StatusLine is null -- should not happen.");
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, response.getStatusLine().toString());
- for (Header h : response.getAllHeaders()) {
- Log.d(TAG, h.getName() + ": " + h.getValue());
- }
- }
- status = statusLine.getStatusCode();
-
- HttpEntity entity = response.getEntity();
-
- if ((status >= 200) && (status < 300) && entity != null) {
- InputStream in = AndroidHttpClient.getUngzippedContent(entity);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- in = logInputStreamContents(in);
- }
- return in;
- }
-
- // TODO: handle 301, 307?
- // TODO: let the http client handle the redirects, if we can be sure we'll never get a
- // redirect on POST.
- if (status == 302) {
- // consume the content, so the connection can be closed.
- entity.consumeContent();
- Header location = response.getFirstHeader("Location");
- if (location == null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Redirect requested but no Location "
- + "specified.");
- }
- break;
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Following redirect to " + location.getValue());
- }
- try {
- uri = new URI(location.getValue());
- } catch (URISyntaxException use) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Unable to parse " + location.getValue() + " as URI.", use);
- throw new IOException("Unable to parse " + location.getValue()
- + " as URI.");
- }
- break;
- }
- --redirectsLeft;
- } else {
- break;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Received " + status + " status code.");
- }
- String errorMessage = null;
- HttpEntity entity = response.getEntity();
- try {
- if (response != null && entity != null) {
- InputStream in = AndroidHttpClient.getUngzippedContent(entity);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buf = new byte[8192];
- int bytesRead = -1;
- while ((bytesRead = in.read(buf)) != -1) {
- baos.write(buf, 0, bytesRead);
- }
- // TODO: use appropriate encoding, picked up from Content-Type.
- errorMessage = new String(baos.toByteArray());
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, errorMessage);
- }
- }
- } finally {
- if (entity != null) {
- entity.consumeContent();
- }
- }
- String exceptionMessage = "Received " + status + " status code";
- if (errorMessage != null) {
- exceptionMessage += (": " + errorMessage);
- }
- throw new HttpException(exceptionMessage, status, null /* InputStream */);
- }
-
- /*
- * (non-Javadoc)
- * @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
- */
- public InputStream getFeedAsStream(String feedUrl,
- String authToken,
- String eTag,
- String protocolVersion)
- throws HttpException, IOException {
-
- InputStream in = createAndExecuteMethod(new GetRequestCreator(), feedUrl, authToken, eTag, protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to access feed.");
- }
-
- /**
- * Log the contents of the input stream.
- * The original input stream is consumed, so the caller must use the
- * BufferedInputStream that is returned.
- * @param in InputStream
- * @return replacement input stream for caller to use
- * @throws IOException
- */
- private InputStream logInputStreamContents(InputStream in) throws IOException {
- if (in == null) {
- return in;
- }
- // bufferSize is the (arbitrary) maximum amount to log.
- // The original InputStream is wrapped in a
- // BufferedInputStream with a 16K buffer. This lets
- // us read up to 16K, write it to the log, and then
- // reset the stream so the the original client can
- // then read the data. The BufferedInputStream
- // provides the mark and reset support, even when
- // the original InputStream does not.
- final int bufferSize = 16384;
- BufferedInputStream bin = new BufferedInputStream(in, bufferSize);
- bin.mark(bufferSize);
- int wanted = bufferSize;
- int totalReceived = 0;
- byte buf[] = new byte[wanted];
- while (wanted > 0) {
- int got = bin.read(buf, totalReceived, wanted);
- if (got <= 0) break; // EOF
- wanted -= got;
- totalReceived += got;
- }
- Log.d(TAG, new String(buf, 0, totalReceived, "UTF-8"));
- bin.reset();
- return bin;
- }
-
- public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken, String eTag, String protocolVersion)
- throws HttpException, IOException {
-
- InputStream in = createAndExecuteMethod(new GetRequestCreator(), mediaEntryUrl, authToken, eTag, protocolVersion);
-
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to access media entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#createEntry
- */
- public InputStream createEntry(String feedUrl,
- String authToken,
- String protocolVersion,
- GDataSerializer entry)
- throws HttpException, IOException {
-
- HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_CREATE);
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator(null /* override */, entity),
- feedUrl,
- authToken,
- null,
- protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to create entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#updateEntry
- */
- public InputStream updateEntry(String editUri,
- String authToken,
- String eTag,
- String protocolVersion,
- GDataSerializer entry)
- throws HttpException, IOException {
- HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_UPDATE);
- final String method = entry.getSupportsPartial() ? "PATCH" : "PUT";
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator(method, entity),
- editUri,
- authToken,
- eTag,
- protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to update entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#deleteEntry
- */
- public void deleteEntry(String editUri, String authToken, String eTag)
- throws HttpException, IOException {
- if (StringUtils.isEmpty(editUri)) {
- throw new IllegalArgumentException(
- "you must specify an non-empty edit url");
- }
- InputStream in =
- createAndExecuteMethod(
- new PostRequestCreator("DELETE", null /* entity */),
- editUri,
- authToken,
- eTag,
- null /* protocolVersion, not required for a delete */);
- if (in == null) {
- throw new IOException("Unable to delete entry.");
- }
- try {
- in.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
-
- public InputStream updateMediaEntry(String editUri, String authToken, String eTag,
- String protocolVersion, InputStream mediaEntryInputStream, String contentType)
- throws HttpException, IOException {
- InputStream in = createAndExecuteMethod(
- new MediaPutRequestCreator(mediaEntryInputStream, contentType),
- editUri,
- authToken,
- eTag,
- protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to write media entry.");
- }
-
- private HttpEntity createEntityForEntry(GDataSerializer entry, int format) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- entry.serialize(baos, format);
- } catch (IOException ioe) {
- Log.e(TAG, "Unable to serialize entry.", ioe);
- throw ioe;
- } catch (ParseException pe) {
- Log.e(TAG, "Unable to serialize entry.", pe);
- throw new IOException("Unable to serialize entry: " + pe.getMessage());
- }
-
- byte[] entryBytes = baos.toByteArray();
-
- if (entryBytes != null && Log.isLoggable(TAG, Log.DEBUG)) {
- try {
- Log.d(TAG, "Serialized entry: " + new String(entryBytes, "UTF-8"));
- } catch (UnsupportedEncodingException uee) {
- // should not happen
- throw new IllegalStateException("UTF-8 should be supported!",
- uee);
- }
- }
-
- AbstractHttpEntity entity;
- if (SystemProperties.getBoolean(NO_GZIP_SYSTEM_PROPERTY, false)) {
- entity = new ByteArrayEntity(entryBytes);
- } else {
- entity = AndroidHttpClient.getCompressedEntity(entryBytes, mResolver);
- }
-
- entity.setContentType(entry.getContentType());
- return entity;
- }
-
- /**
- * Connects to a GData server (specified by the batchUrl) and submits a
- * batch for processing. The response from the server is returned as an
- * {@link InputStream}. The caller is responsible for calling
- * {@link InputStream#close()} on the returned {@link InputStream}.
- *
- * @param batchUrl The batch url to which the batch is submitted.
- * @param authToken the authentication token that should be used when
- * submitting the batch.
- * @param protocolVersion The version of the protocol that
- * should be used for this request.
- * @param batch The batch of entries to submit.
- * @throws IOException Thrown if an io error occurs while communicating with
- * the service.
- * @throws HttpException if the service returns an error response.
- */
- public InputStream submitBatch(String batchUrl,
- String authToken,
- String protocolVersion,
- GDataSerializer batch)
- throws HttpException, IOException
- {
- HttpEntity entity = createEntityForEntry(batch, GDataSerializer.FORMAT_BATCH);
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator("POST", entity),
- batchUrl,
- authToken,
- null,
- protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to process batch request.");
- }
-}
diff --git a/core/java/com/google/android/gdata2/client/AndroidXmlParserFactory.java b/core/java/com/google/android/gdata2/client/AndroidXmlParserFactory.java
deleted file mode 100644
index f097706..0000000
--- a/core/java/com/google/android/gdata2/client/AndroidXmlParserFactory.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.google.android.gdata2.client;
-
-import com.google.wireless.gdata2.parser.xml.XmlParserFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Xml;
-
-/**
- * XmlParserFactory for the Android platform.
- */
-public class AndroidXmlParserFactory implements XmlParserFactory {
-
- /*
- * (non-javadoc)
- * @see XmlParserFactory#createParser
- */
- public XmlPullParser createParser() throws XmlPullParserException {
- return Xml.newPullParser();
- }
-
- /*
- * (non-javadoc)
- * @see XmlParserFactory#createSerializer
- */
- public XmlSerializer createSerializer() throws XmlPullParserException {
- return Xml.newSerializer();
- }
-}
diff --git a/core/java/com/google/android/gdata2/client/QueryParamsImpl.java b/core/java/com/google/android/gdata2/client/QueryParamsImpl.java
deleted file mode 100644
index a26f4ce..0000000
--- a/core/java/com/google/android/gdata2/client/QueryParamsImpl.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.google.android.gdata2.client;
-import com.google.wireless.gdata2.client.QueryParams;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Simple implementation of the QueryParams interface.
- */
-// TODO: deal with categories
-public class QueryParamsImpl extends QueryParams {
-
- private final Map<String,String> mParams = new HashMap<String,String>();
-
- /**
- * Creates a new empty QueryParamsImpl.
- */
- public QueryParamsImpl() {
- }
-
- @Override
- public void clear() {
- setEntryId(null);
- mParams.clear();
- }
-
- @Override
- public String generateQueryUrl(String feedUrl) {
-
- if (TextUtils.isEmpty(getEntryId()) &&
- mParams.isEmpty()) {
- // nothing to do
- return feedUrl;
- }
-
- // handle entry IDs
- if (!TextUtils.isEmpty(getEntryId())) {
- if (!mParams.isEmpty()) {
- throw new IllegalStateException("Cannot set both an entry ID "
- + "and other query paramters.");
- }
- return feedUrl + '/' + getEntryId();
- }
-
- // otherwise, append the querystring params.
- StringBuilder sb = new StringBuilder();
- sb.append(feedUrl);
- Set<String> params = mParams.keySet();
- boolean first = true;
- if (feedUrl.contains("?")) {
- first = false;
- } else {
- sb.append('?');
- }
- for (String param : params) {
- if (first) {
- first = false;
- } else {
- sb.append('&');
- }
- sb.append(param);
- sb.append('=');
- String value = mParams.get(param);
- String encodedValue = null;
-
- try {
- encodedValue = URLEncoder.encode(value, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- // should not happen.
- Log.w("QueryParamsImpl",
- "UTF-8 not supported -- should not happen. "
- + "Using default encoding.", uee);
- encodedValue = URLEncoder.encode(value);
- }
- sb.append(encodedValue);
- }
- return sb.toString();
- }
-
- @Override
- public String getParamValue(String param) {
- if (!(mParams.containsKey(param))) {
- return null;
- }
- return mParams.get(param);
- }
-
- @Override
- public void setParamValue(String param, String value) {
- mParams.put(param, value);
- }
-
-}
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
index 2a1f23a..1f754bc 100644
--- a/core/java/com/google/android/mms/pdu/PduPersister.java
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -424,6 +424,9 @@ public class PduPersister {
// faster.
if ("text/plain".equals(type) || "application/smil".equals(type)) {
String text = c.getString(PART_COLUMN_TEXT);
+ if (text == null) {
+ text = "";
+ }
byte [] blob = new EncodedStringValue(text).getTextString();
baos.write(blob, 0, blob.length);
} else {
@@ -858,7 +861,7 @@ public class PduPersister {
} else {
values.put(Mms.SUBJECT, "");
}
-
+
long messageSize = sendReq.getMessageSize();
if (messageSize > 0) {
values.put(Mms.MESSAGE_SIZE, messageSize);
diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java
deleted file mode 100644
index 8a1298f..0000000
--- a/core/java/com/google/android/net/GoogleHttpClient.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * 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.
- */
-
-package com.google.android.net;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.http.AndroidHttpClient;
-import android.os.Build;
-import android.os.NetStat;
-import android.os.SystemClock;
-import android.provider.Checkin;
-import android.util.Config;
-import android.util.Log;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpEntityEnclosingRequest;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.ProtocolException;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.scheme.LayeredSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.scheme.SocketFactory;
-import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
-import org.apache.http.impl.client.RequestWrapper;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HttpContext;
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * {@link AndroidHttpClient} wrapper that uses {@link UrlRules} to rewrite URLs
- * and otherwise tweak HTTP requests.
- */
-public class GoogleHttpClient implements HttpClient {
- private static final String TAG = "GoogleHttpClient";
- private static final boolean LOCAL_LOGV = Config.LOGV || false;
-
- /** Exception thrown when a request is blocked by the URL rules. */
- public static class BlockedRequestException extends IOException {
- private final UrlRules.Rule mRule;
- BlockedRequestException(UrlRules.Rule rule) {
- super("Blocked by rule: " + rule.mName);
- mRule = rule;
- }
- }
-
- private final AndroidHttpClient mClient;
- private final ContentResolver mResolver;
- private final String mAppName, mUserAgent;
- private final ThreadLocal<Boolean> mConnectionAllocated = new ThreadLocal<Boolean>();
-
- /**
- * Create an HTTP client without SSL session persistence.
- * @deprecated Use {@link #GoogleHttpClient(android.content.Context, String, boolean)}
- */
- public GoogleHttpClient(ContentResolver resolver, String userAgent) {
- mClient = AndroidHttpClient.newInstance(userAgent);
- mResolver = resolver;
- mUserAgent = mAppName = userAgent;
- }
-
- /**
- * Create an HTTP client without SSL session persistence.
- * @deprecated Use {@link #GoogleHttpClient(android.content.Context, String, boolean)}
- */
- public GoogleHttpClient(ContentResolver resolver, String appAndVersion,
- boolean gzipCapable) {
- this(resolver, null /* cache */, appAndVersion, gzipCapable);
- }
-
- /**
- * Create an HTTP client. Normaly this client is shared throughout an app.
- * The HTTP client will construct its User-Agent as follows:
- *
- * <appAndVersion> (<build device> <build id>)
- * or
- * <appAndVersion> (<build device> <build id>); gzip
- * (if gzip capable)
- *
- * The context has settings for URL rewriting rules and is used to enable
- * SSL session persistence.
- *
- * @param context application context.
- * @param appAndVersion Base app and version to use in the User-Agent.
- * e.g., "MyApp/1.0"
- * @param gzipCapable Whether or not this client is able to consume gzip'd
- * responses. Only used to modify the User-Agent, not other request
- * headers. Needed because Google servers require gzip in the User-Agent
- * in order to return gzip'd content.
- */
- public GoogleHttpClient(Context context, String appAndVersion, boolean gzipCapable) {
- this(context.getContentResolver(),
- SSLClientSessionCacheFactory.getCache(context),
- appAndVersion, gzipCapable);
- }
-
- private GoogleHttpClient(ContentResolver resolver,
- SSLClientSessionCache cache,
- String appAndVersion, boolean gzipCapable) {
- String userAgent = appAndVersion + " (" + Build.DEVICE + " " + Build.ID + ")";
- if (gzipCapable) {
- userAgent = userAgent + "; gzip";
- }
-
- mClient = AndroidHttpClient.newInstance(userAgent, cache);
- mResolver = resolver;
- mAppName = appAndVersion;
- mUserAgent = userAgent;
-
- // Wrap all the socket factories with the appropriate wrapper. (Apache
- // HTTP, curse its black and stupid heart, inspects the SocketFactory to
- // see if it's a LayeredSocketFactory, so we need two wrapper classes.)
- SchemeRegistry registry = getConnectionManager().getSchemeRegistry();
- for (String name : registry.getSchemeNames()) {
- Scheme scheme = registry.unregister(name);
- SocketFactory sf = scheme.getSocketFactory();
- if (sf instanceof LayeredSocketFactory) {
- sf = new WrappedLayeredSocketFactory((LayeredSocketFactory) sf);
- } else {
- sf = new WrappedSocketFactory(sf);
- }
- registry.register(new Scheme(name, sf, scheme.getDefaultPort()));
- }
- }
-
- /**
- * Delegating wrapper for SocketFactory records when sockets are connected.
- * We use this to know whether a connection was created vs reused, to
- * gather per-app statistics about connection reuse rates.
- * (Note, we record only *connection*, not *creation* of sockets --
- * what we care about is the network overhead of an actual TCP connect.)
- */
- private class WrappedSocketFactory implements SocketFactory {
- private SocketFactory mDelegate;
- private WrappedSocketFactory(SocketFactory delegate) { mDelegate = delegate; }
- public final Socket createSocket() throws IOException { return mDelegate.createSocket(); }
- public final boolean isSecure(Socket s) { return mDelegate.isSecure(s); }
-
- public final Socket connectSocket(
- Socket s, String h, int p,
- InetAddress la, int lp, HttpParams params) throws IOException {
- mConnectionAllocated.set(Boolean.TRUE);
- return mDelegate.connectSocket(s, h, p, la, lp, params);
- }
- }
-
- /** Like WrappedSocketFactory, but for the LayeredSocketFactory subclass. */
- private class WrappedLayeredSocketFactory
- extends WrappedSocketFactory implements LayeredSocketFactory {
- private LayeredSocketFactory mDelegate;
- private WrappedLayeredSocketFactory(LayeredSocketFactory sf) { super(sf); mDelegate = sf; }
-
- public final Socket createSocket(Socket s, String host, int port, boolean autoClose)
- throws IOException {
- return mDelegate.createSocket(s, host, port, autoClose);
- }
- }
-
- /**
- * Release resources associated with this client. You must call this,
- * or significant resources (sockets and memory) may be leaked.
- */
- public void close() {
- mClient.close();
- }
-
- /** Execute a request without applying and rewrite rules. */
- public HttpResponse executeWithoutRewriting(
- HttpUriRequest request, HttpContext context)
- throws IOException {
- int code = -1;
- long start = SystemClock.elapsedRealtime();
- try {
- HttpResponse response;
- mConnectionAllocated.set(null);
-
- if (NetworkStatsEntity.shouldLogNetworkStats()) {
- // TODO: if we're logging network stats, and if the apache library is configured
- // to follow redirects, count each redirect as an additional round trip.
-
- int uid = android.os.Process.myUid();
- long startTx = NetStat.getUidTxBytes(uid);
- long startRx = NetStat.getUidRxBytes(uid);
-
- response = mClient.execute(request, context);
- HttpEntity origEntity = response == null ? null : response.getEntity();
- if (origEntity != null) {
- // yeah, we compute the same thing below. we do need to compute this here
- // so we can wrap the HttpEntity in the response.
- long now = SystemClock.elapsedRealtime();
- long elapsed = now - start;
- NetworkStatsEntity entity = new NetworkStatsEntity(origEntity,
- mAppName, uid, startTx, startRx,
- elapsed /* response latency */, now /* processing start time */);
- response.setEntity(entity);
- }
- } else {
- response = mClient.execute(request, context);
- }
-
- code = response.getStatusLine().getStatusCode();
- return response;
- } finally {
- // Record some statistics to the checkin service about the outcome.
- // Note that this is only describing execute(), not body download.
- // We assume the database writes are much faster than network I/O,
- // and not worth running in a background thread or anything.
- try {
- long elapsed = SystemClock.elapsedRealtime() - start;
- ContentValues values = new ContentValues();
- values.put(Checkin.Stats.COUNT, 1);
- values.put(Checkin.Stats.SUM, elapsed / 1000.0);
-
- values.put(Checkin.Stats.TAG, Checkin.Stats.Tag.HTTP_REQUEST + ":" + mAppName);
- mResolver.insert(Checkin.Stats.CONTENT_URI, values);
-
- // No sockets and no exceptions means we successfully reused a connection
- if (mConnectionAllocated.get() == null && code >= 0) {
- values.put(Checkin.Stats.TAG, Checkin.Stats.Tag.HTTP_REUSED + ":" + mAppName);
- mResolver.insert(Checkin.Stats.CONTENT_URI, values);
- }
-
- String status = code < 0 ? "IOException" : Integer.toString(code);
- values.put(Checkin.Stats.TAG,
- Checkin.Stats.Tag.HTTP_STATUS + ":" + mAppName + ":" + status);
- mResolver.insert(Checkin.Stats.CONTENT_URI, values);
- } catch (Exception e) {
- Log.e(TAG, "Error recording stats", e);
- }
- }
- }
-
- public String rewriteURI(String original) {
- UrlRules rules = UrlRules.getRules(mResolver);
- UrlRules.Rule rule = rules.matchRule(original);
- return rule.apply(original);
- }
-
- public HttpResponse execute(HttpUriRequest request, HttpContext context)
- throws IOException {
- // Rewrite the supplied URL...
- URI uri = request.getURI();
- String original = uri.toString();
- UrlRules rules = UrlRules.getRules(mResolver);
- UrlRules.Rule rule = rules.matchRule(original);
- String rewritten = rule.apply(original);
-
- if (rewritten == null) {
- Log.w(TAG, "Blocked by " + rule.mName + ": " + original);
- throw new BlockedRequestException(rule);
- } else if (rewritten == original) {
- return executeWithoutRewriting(request, context); // Pass through
- }
-
- try {
- uri = new URI(rewritten);
- } catch (URISyntaxException e) {
- throw new RuntimeException("Bad URL from rule: " + rule.mName, e);
- }
-
- // Wrap request so we can replace the URI.
- RequestWrapper wrapper = wrapRequest(request);
- wrapper.setURI(uri);
- request = wrapper;
-
- if (LOCAL_LOGV) Log.v(TAG, "Rule " + rule.mName + ": " + original + " -> " + rewritten);
- return executeWithoutRewriting(request, context);
- }
-
- /**
- * Wraps the request making it mutable.
- */
- private static RequestWrapper wrapRequest(HttpUriRequest request)
- throws IOException {
- try {
- // We have to wrap it with the right type. Some code performs
- // instanceof checks.
- RequestWrapper wrapped;
- if (request instanceof HttpEntityEnclosingRequest) {
- wrapped = new EntityEnclosingRequestWrapper(
- (HttpEntityEnclosingRequest) request);
- } else {
- wrapped = new RequestWrapper(request);
- }
-
- // Copy the headers from the original request into the wrapper.
- wrapped.resetHeaders();
-
- return wrapped;
- } catch (ProtocolException e) {
- throw new ClientProtocolException(e);
- }
- }
-
- /**
- * Mark a user agent as one Google will trust to handle gzipped content.
- * {@link AndroidHttpClient#modifyRequestToAcceptGzipResponse} is (also)
- * necessary but not sufficient -- many browsers claim to accept gzip but
- * have broken handling, so Google checks the user agent as well.
- *
- * @param originalUserAgent to modify (however you identify yourself)
- * @return user agent with a "yes, I really can handle gzip" token added.
- * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)}
- */
- public static String getGzipCapableUserAgent(String originalUserAgent) {
- return originalUserAgent + "; gzip";
- }
-
- // HttpClient wrapper methods.
-
- public HttpParams getParams() {
- return mClient.getParams();
- }
-
- public ClientConnectionManager getConnectionManager() {
- return mClient.getConnectionManager();
- }
-
- public HttpResponse execute(HttpUriRequest request) throws IOException {
- return execute(request, (HttpContext) null);
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request)
- throws IOException {
- return mClient.execute(target, request);
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request,
- HttpContext context) throws IOException {
- return mClient.execute(target, request, context);
- }
-
- public <T> T execute(HttpUriRequest request,
- ResponseHandler<? extends T> responseHandler)
- throws IOException, ClientProtocolException {
- return mClient.execute(request, responseHandler);
- }
-
- public <T> T execute(HttpUriRequest request,
- ResponseHandler<? extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- return mClient.execute(request, responseHandler, context);
- }
-
- public <T> T execute(HttpHost target, HttpRequest request,
- ResponseHandler<? extends T> responseHandler) throws IOException,
- ClientProtocolException {
- return mClient.execute(target, request, responseHandler);
- }
-
- public <T> T execute(HttpHost target, HttpRequest request,
- ResponseHandler<? extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- return mClient.execute(target, request, responseHandler, context);
- }
-
- /**
- * Enables cURL request logging for this client.
- *
- * @param name to log messages with
- * @param level at which to log messages (see {@link android.util.Log})
- */
- public void enableCurlLogging(String name, int level) {
- mClient.enableCurlLogging(name, level);
- }
-
- /**
- * Disables cURL logging for this client.
- */
- public void disableCurlLogging() {
- mClient.disableCurlLogging();
- }
-}
diff --git a/core/java/com/google/android/net/NetworkStatsEntity.java b/core/java/com/google/android/net/NetworkStatsEntity.java
deleted file mode 100644
index f5d2349..0000000
--- a/core/java/com/google/android/net/NetworkStatsEntity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.google.android.net;
-
-import android.os.NetStat;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.util.EventLog;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.entity.HttpEntityWrapper;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-
-public class NetworkStatsEntity extends HttpEntityWrapper {
-
- private static final int HTTP_STATS_EVENT = 52001;
-
- private class NetworkStatsInputStream extends FilterInputStream {
-
- public NetworkStatsInputStream(InputStream wrapped) {
- super(wrapped);
- }
-
- @Override
- public void close() throws IOException {
- try {
- super.close();
- } finally {
- long processingTime = SystemClock.elapsedRealtime() - mProcessingStartTime;
- long tx = NetStat.getUidTxBytes(mUid);
- long rx = NetStat.getUidRxBytes(mUid);
-
- EventLog.writeEvent(HTTP_STATS_EVENT, mUa, mResponseLatency, processingTime,
- tx - mStartTx, rx - mStartRx);
- }
- }
- }
-
- private final String mUa;
- private final int mUid;
- private final long mStartTx;
- private final long mStartRx;
- private final long mResponseLatency;
- private final long mProcessingStartTime;
-
- public NetworkStatsEntity(HttpEntity orig, String ua,
- int uid, long startTx, long startRx, long responseLatency,
- long processingStartTime) {
- super(orig);
- this.mUa = ua;
- this.mUid = uid;
- this.mStartTx = startTx;
- this.mStartRx = startRx;
- this.mResponseLatency = responseLatency;
- this.mProcessingStartTime = processingStartTime;
- }
-
- public static boolean shouldLogNetworkStats() {
- return "1".equals(SystemProperties.get("googlehttpclient.logstats"));
- }
-
- @Override
- public InputStream getContent() throws IOException {
- InputStream orig = super.getContent();
- return new NetworkStatsInputStream(orig);
- }
-}
diff --git a/core/java/com/google/android/net/SSLClientSessionCacheFactory.java b/core/java/com/google/android/net/SSLClientSessionCacheFactory.java
deleted file mode 100644
index 6570a9bd..0000000
--- a/core/java/com/google/android/net/SSLClientSessionCacheFactory.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.google.android.net;
-
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
-import android.content.Context;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-
-import com.android.internal.net.DbSSLSessionCache;
-
-/**
- * Factory that returns the appropriate implementation of a {@link SSLClientSessionCache} based
- * on gservices.
- *
- * @hide
- */
-// TODO: return a proxied implementation that is updated as the gservices value changes.
-public final class SSLClientSessionCacheFactory {
-
- private static final String TAG = "SSLClientSessionCacheFactory";
-
- public static final String DB = "db";
- public static final String FILE = "file";
-
- // utility class
- private SSLClientSessionCacheFactory() {}
-
- /**
- * Returns a new {@link SSLClientSessionCache} based on the persistent cache that's specified,
- * if any, in gservices. If no cache is specified, returns null.
- * @param context The application context used for the per-process persistent cache.
- * @return A new {@link SSLClientSessionCache}, or null if no persistent cache is configured.
- */
- public static SSLClientSessionCache getCache(Context context) {
- String type = Settings.Gservices.getString(context.getContentResolver(),
- Settings.Gservices.SSL_SESSION_CACHE);
-
- if (type != null) {
- if (DB.equals(type)) {
- return DbSSLSessionCache.getInstanceForPackage(context);
- } else if (FILE.equals(type)) {
- File dir = context.getFilesDir();
- File cacheDir = new File(dir, "sslcache");
- if (!cacheDir.exists()) {
- cacheDir.mkdir();
- }
- try {
- return FileClientSessionCache.usingDirectory(cacheDir);
- } catch (IOException ioe) {
- Log.w(TAG, "Unable to create FileClientSessionCache in " + cacheDir.getName(), ioe);
- return null;
- }
- } else {
- Log.w(TAG, "Ignoring unrecognized type: '" + type + "'");
- }
- }
- return null;
- }
-}
diff --git a/core/java/com/google/android/net/UrlRules.java b/core/java/com/google/android/net/UrlRules.java
deleted file mode 100644
index 54d139d..0000000
--- a/core/java/com/google/android/net/UrlRules.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * 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.
- */
-
-package com.google.android.net;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.provider.Checkin;
-import android.provider.Settings;
-import android.util.Config;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A set of rules rewriting and blocking URLs. Used to offer a point of
- * control for redirecting HTTP requests, often to the Android proxy server.
- *
- * <p>Each rule has the following format:
- *
- * <pre><em>url-prefix</em> [REWRITE <em>new-prefix</em>] [BLOCK]</pre>
- *
- * <p>Any URL which starts with <em>url-prefix</em> will trigger the rule.
- * If BLOCK is specified, requests to that URL will be blocked and fail.
- * If REWRITE is specified, the matching prefix will be removed and replaced
- * with <em>new-prefix</em>. (If both are specified, BLOCK wins.) Case is
- * insensitive for the REWRITE and BLOCK keywords, but sensitive for URLs.
- *
- * <p>In Gservices, the value of any key that starts with "url:" will be
- * interpreted as a rule. The full name of the key is unimportant (but can
- * be used to document the intent of the rule, and must be unique).
- * Example gservices keys:
- *
- * <pre>
- * url:use_proxy_for_calendar = "http://www.google.com/calendar/ REWRITE http://android.clients.google.com/proxy/calendar/"
- * url:stop_crash_reports = "http://android.clients.google.com/crash/ BLOCK"
- * url:use_ssl_for_contacts = "http://www.google.com/m8/ REWRITE https://www.google.com/m8/"
- * </pre>
- */
-public class UrlRules {
- public static final String TAG = "UrlRules";
- public static final boolean LOCAL_LOGV = Config.LOGV || false;
-
- /** Thrown when the rewrite rules can't be parsed. */
- public static class RuleFormatException extends Exception {
- public RuleFormatException(String msg) { super(msg); }
- }
-
- /** A single rule specifying actions for URLs matching a certain prefix. */
- public static class Rule implements Comparable {
- /** Name assigned to the rule (for logging and debugging). */
- public final String mName;
-
- /** Prefix required to match this rule. */
- public final String mPrefix;
-
- /** Text to replace mPrefix with (null to leave alone). */
- public final String mRewrite;
-
- /** True if matching URLs should be blocked. */
- public final boolean mBlock;
-
- /** Default rule that does nothing. */
- public static final Rule DEFAULT = new Rule();
-
- /** Parse a rewrite rule as given in a Gservices value. */
- public Rule(String name, String rule) throws RuleFormatException {
- mName = name;
- String[] words = PATTERN_SPACE_PLUS.split(rule);
- if (words.length == 0) throw new RuleFormatException("Empty rule");
-
- mPrefix = words[0];
- String rewrite = null;
- boolean block = false;
- for (int pos = 1; pos < words.length; ) {
- String word = words[pos].toLowerCase();
- if (word.equals("rewrite") && pos + 1 < words.length) {
- rewrite = words[pos + 1];
- pos += 2;
- } else if (word.equals("block")) {
- block = true;
- pos += 1;
- } else {
- throw new RuleFormatException("Illegal rule: " + rule);
- }
- // TODO: Parse timeout specifications, etc.
- }
-
- mRewrite = rewrite;
- mBlock = block;
- }
-
- /** Create the default Rule. */
- private Rule() {
- mName = "DEFAULT";
- mPrefix = "";
- mRewrite = null;
- mBlock = false;
- }
-
- /**
- * Apply the rule to a particular URL (assumed to match the rule).
- * @param url to rewrite or modify.
- * @return modified URL, or null if the URL is blocked.
- */
- public String apply(String url) {
- if (mBlock) {
- return null;
- } else if (mRewrite != null) {
- return mRewrite + url.substring(mPrefix.length());
- } else {
- return url;
- }
- }
-
- /** More generic rules are greater than more specific rules. */
- public int compareTo(Object o) {
- return ((Rule) o).mPrefix.compareTo(mPrefix);
- }
- }
-
- /** Cached rule set from Gservices. */
- private static UrlRules sCachedRules = new UrlRules(new Rule[] {});
-
- private static final Pattern PATTERN_SPACE_PLUS = Pattern.compile(" +");
- private static final Pattern RULE_PATTERN = Pattern.compile("\\W");
-
- /** Gservices digest when sCachedRules was cached. */
- private static String sCachedDigest = null;
-
- /** Currently active set of Rules. */
- private final Rule[] mRules;
-
- /** Regular expression with one capturing group for each Rule. */
- private final Pattern mPattern;
-
- /**
- * Create a rewriter from an array of Rules. Normally used only for
- * testing. Instead, use {@link #getRules} to get rules from Gservices.
- * @param rules to use.
- */
- public UrlRules(Rule[] rules) {
- // Sort the rules to put the most specific rules first.
- Arrays.sort(rules);
-
- // Construct a regular expression, escaping all the prefix strings.
- StringBuilder pattern = new StringBuilder("(");
- for (int i = 0; i < rules.length; ++i) {
- if (i > 0) pattern.append(")|(");
- pattern.append(RULE_PATTERN.matcher(rules[i].mPrefix).replaceAll("\\\\$0"));
- }
- mPattern = Pattern.compile(pattern.append(")").toString());
- mRules = rules;
- }
-
- /**
- * Match a string against every Rule and find one that matches.
- * @param uri to match against the Rules in the rewriter.
- * @return the most specific matching Rule, or Rule.DEFAULT if none match.
- */
- public Rule matchRule(String url) {
- Matcher matcher = mPattern.matcher(url);
- if (matcher.lookingAt()) {
- for (int i = 0; i < mRules.length; ++i) {
- if (matcher.group(i + 1) != null) {
- return mRules[i]; // Rules are sorted most specific first.
- }
- }
- }
- return Rule.DEFAULT;
- }
-
- /**
- * Get the (possibly cached) UrlRules based on the rules in Gservices.
- * @param resolver to use for accessing the Gservices database.
- * @return an updated UrlRules instance
- */
- public static synchronized UrlRules getRules(ContentResolver resolver) {
- String digest = Settings.Gservices.getString(resolver,
- Settings.Gservices.PROVISIONING_DIGEST);
- if (sCachedDigest != null && sCachedDigest.equals(digest)) {
- // The digest is the same, so the rules are the same.
- if (LOCAL_LOGV) Log.v(TAG, "Using cached rules for digest: " + digest);
- return sCachedRules;
- }
-
- if (LOCAL_LOGV) Log.v(TAG, "Scanning for Gservices \"url:*\" rules");
- Cursor cursor = resolver.query(Settings.Gservices.CONTENT_URI,
- new String[] {
- Settings.Gservices.NAME,
- Settings.Gservices.VALUE
- },
- Settings.Gservices.NAME + " like \"url:%\"", null,
- Settings.Gservices.NAME);
- try {
- ArrayList<Rule> rules = new ArrayList<Rule>();
- while (cursor.moveToNext()) {
- try {
- String name = cursor.getString(0).substring(4); // "url:X"
- String value = cursor.getString(1);
- if (value == null || value.length() == 0) continue;
- if (LOCAL_LOGV) Log.v(TAG, " Rule " + name + ": " + value);
- rules.add(new Rule(name, value));
- } catch (RuleFormatException e) {
- // Oops, Gservices has an invalid rule! Skip it.
- Log.e(TAG, "Invalid rule from Gservices", e);
- Checkin.logEvent(resolver,
- Checkin.Events.Tag.GSERVICES_ERROR, e.toString());
- }
- }
- sCachedRules = new UrlRules(rules.toArray(new Rule[rules.size()]));
- sCachedDigest = digest;
- if (LOCAL_LOGV) Log.v(TAG, "New rules stored for digest: " + digest);
- } finally {
- cursor.close();
- }
-
- return sCachedRules;
- }
-}
diff --git a/core/java/com/google/android/util/GoogleWebContentHelper.java b/core/java/com/google/android/util/GoogleWebContentHelper.java
deleted file mode 100644
index 8291e29..0000000
--- a/core/java/com/google/android/util/GoogleWebContentHelper.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * 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.
- */
-
-package com.google.android.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.http.SslError;
-import android.os.Message;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.HttpAuthHandler;
-import android.webkit.SslErrorHandler;
-import android.webkit.WebSettings;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.TextView;
-
-import java.util.Locale;
-
-/**
- * Helper to display Google web content, and fallback on a static message if the
- * web content is unreachable. For example, this can be used to display
- * "Legal terms".
- * <p>
- * The typical usage pattern is to have two Gservices settings defined:
- * <ul>
- * <li>A secure URL that will be displayed on the device. This should be HTTPS
- * so hotspots won't intercept it giving us a false positive that the page
- * loaded successfully.
- * <li>A pretty human-readable URL that will be displayed to the user in case we
- * cannot reach the above URL.
- * </ul>
- * <p>
- * The typical call sequence is {@link #setUrlsFromGservices(String, String)},
- * {@link #setUnsuccessfulMessage(String)}, and {@link #loadUrl()}. At some
- * point, you'll want to display the layout via {@link #getLayout()}.
- */
-public class GoogleWebContentHelper {
-
- private Context mContext;
-
- private String mSecureUrl;
- private String mPrettyUrl;
-
- private String mUnsuccessfulMessage;
-
- private ViewGroup mLayout;
- private WebView mWebView;
- private View mProgressBar;
- private TextView mTextView;
-
- private boolean mReceivedResponse;
-
- public GoogleWebContentHelper(Context context) {
- mContext = context;
- }
-
- /**
- * Fetches the URLs from Gservices.
- *
- * @param secureSetting The setting key whose value contains the HTTPS URL.
- * @param prettySetting The setting key whose value contains the pretty URL.
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper setUrlsFromGservices(String secureSetting, String prettySetting) {
- ContentResolver contentResolver = mContext.getContentResolver();
- mSecureUrl = fillUrl(Settings.Gservices.getString(contentResolver, secureSetting),
- mContext);
- mPrettyUrl = fillUrl(Settings.Gservices.getString(contentResolver, prettySetting),
- mContext);
- return this;
- }
-
- /**
- * Fetch directly from provided urls.
- *
- * @param secureUrl The HTTPS URL.
- * @param prettyUrl The pretty URL.
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper setUrls(String secureUrl, String prettyUrl) {
- mSecureUrl = fillUrl(secureUrl, mContext);
- mPrettyUrl = fillUrl(prettyUrl, mContext);
- return this;
- }
-
-
- /**
- * Sets the message that will be shown if we are unable to load the page.
- * <p>
- * This should be called after {@link #setUrlsFromGservices(String, String)}
- * .
- *
- * @param message The message to load. The first argument, according to
- * {@link java.util.Formatter}, will be substituted with the pretty
- * URL.
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper setUnsuccessfulMessage(String message) {
- Locale locale = mContext.getResources().getConfiguration().locale;
- mUnsuccessfulMessage = String.format(locale, message, mPrettyUrl);
- return this;
- }
-
- /**
- * Begins loading the secure URL.
- *
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper loadUrl() {
- ensureViews();
- mWebView.loadUrl(mSecureUrl);
- return this;
- }
-
- /**
- * Loads data into the webview and also provides a failback url
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper loadDataWithFailUrl(String base, String data,
- String mimeType, String encoding, String failUrl) {
- ensureViews();
- mWebView.loadDataWithBaseURL(base, data, mimeType, encoding, failUrl);
- return this;
- }
-
- /**
- * Helper to handle the back key. Returns true if the back key was handled,
- * otherwise returns false.
- * @param event the key event sent to {@link Activity#dispatchKeyEvent()}
- */
- public boolean handleKey(KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
- && event.getAction() == KeyEvent.ACTION_DOWN) {
- if (mWebView.canGoBack()) {
- mWebView.goBack();
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the layout containing the web view, progress bar, and text view.
- * This class takes care of setting each one's visibility based on current
- * state.
- *
- * @return The layout you should display.
- */
- public ViewGroup getLayout() {
- ensureViews();
- return mLayout;
- }
-
- private synchronized void ensureViews() {
- if (mLayout == null) {
- initializeViews();
- }
- }
-
- /**
- * Fills the URL with the locale.
- *
- * @param url The URL in Formatter style for the extra info to be filled in.
- * @return The filled URL.
- */
- private static String fillUrl(String url, Context context) {
-
- if (TextUtils.isEmpty(url)) {
- return "";
- }
-
- /* We add another layer of indirection here to allow mcc's to fill
- * in Locales for TOS. TODO - REMOVE when needed locales supported
- * natively (when not shipping devices to country X without support
- * for their locale).
- */
- String localeReplacement = context.
- getString(com.android.internal.R.string.locale_replacement);
- if (localeReplacement != null && localeReplacement.length() != 0) {
- url = String.format(url, localeReplacement);
- }
-
- Locale locale = Locale.getDefault();
- String tmp = locale.getLanguage() + "_" + locale.getCountry().toLowerCase();
- return String.format(url, tmp);
- }
-
- private void initializeViews() {
-
- LayoutInflater inflater =
- (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- mLayout = (ViewGroup) inflater.inflate(
- com.android.internal.R.layout.google_web_content_helper_layout, null);
-
- mWebView = (WebView) mLayout.findViewById(com.android.internal.R.id.web);
- mWebView.setWebViewClient(new MyWebViewClient());
- WebSettings settings = mWebView.getSettings();
- settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
-
- mProgressBar = mLayout.findViewById(com.android.internal.R.id.progressContainer);
- TextView message = (TextView) mProgressBar.findViewById(com.android.internal.R.id.message);
- message.setText(com.android.internal.R.string.googlewebcontenthelper_loading);
-
- mTextView = (TextView) mLayout.findViewById(com.android.internal.R.id.text);
- mTextView.setText(mUnsuccessfulMessage);
- }
-
- private synchronized void handleWebViewCompletion(boolean success) {
-
- if (mReceivedResponse) {
- return;
- } else {
- mReceivedResponse = true;
- }
-
- // In both cases, remove the progress bar
- ((ViewGroup) mProgressBar.getParent()).removeView(mProgressBar);
-
- // Remove the view that isn't relevant
- View goneView = success ? mTextView : mWebView;
- ((ViewGroup) goneView.getParent()).removeView(goneView);
-
- // Show the next view, which depends on success
- View visibleView = success ? mWebView : mTextView;
- visibleView.setVisibility(View.VISIBLE);
- }
-
- private class MyWebViewClient extends WebViewClient {
-
- @Override
- public void onPageFinished(WebView view, String url) {
- handleWebViewCompletion(true);
- }
-
- @Override
- public void onReceivedError(WebView view, int errorCode,
- String description, String failingUrl) {
- handleWebViewCompletion(false);
- }
-
- @Override
- public void onReceivedHttpAuthRequest(WebView view,
- HttpAuthHandler handler, String host, String realm) {
- handleWebViewCompletion(false);
- }
-
- @Override
- public void onReceivedSslError(WebView view, SslErrorHandler handler,
- SslError error) {
- handleWebViewCompletion(false);
- }
-
- @Override
- public void onTooManyRedirects(WebView view, Message cancelMsg,
- Message continueMsg) {
- handleWebViewCompletion(false);
- }
-
- }
-
-}
diff --git a/core/java/com/google/android/util/SimplePullParser.java b/core/java/com/google/android/util/SimplePullParser.java
deleted file mode 100644
index 031790b..0000000
--- a/core/java/com/google/android/util/SimplePullParser.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * 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.
- */
-
-package com.google.android.util;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.io.Reader;
-import java.io.Closeable;
-
-import android.util.Xml;
-import android.util.Log;
-
-/**
- * This is an abstraction of a pull parser that provides several benefits:<ul>
- * <li>it is easier to use robustly because it makes it trivial to handle unexpected tags (which
- * might have children)</li>
- * <li>it makes the handling of text (cdata) blocks more convenient</li>
- * <li>it provides convenient methods for getting a mandatory attribute (and throwing an exception
- * if it is missing) or an optional attribute (and using a default value if it is missing)
- * </ul>
- */
-public class SimplePullParser {
- public static final String TEXT_TAG = "![CDATA[";
-
- private String mLogTag = null;
- private final XmlPullParser mParser;
- private Closeable source;
- private String mCurrentStartTag;
-
- /**
- * Constructs a new SimplePullParser to parse the stream
- * @param stream stream to parse
- * @param encoding the encoding to use
- */
- public SimplePullParser(InputStream stream, String encoding)
- throws ParseException, IOException {
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(stream, encoding);
- moveToStartDocument(parser);
- mParser = parser;
- mCurrentStartTag = null;
- source = stream;
- } catch (XmlPullParserException e) {
- throw new ParseException(e);
- }
- }
-
- /**
- * Constructs a new SimplePullParser to parse the xml
- * @param parser the underlying parser to use
- */
- public SimplePullParser(XmlPullParser parser) {
- mParser = parser;
- mCurrentStartTag = null;
- source = null;
- }
-
- /**
- * Constructs a new SimplePullParser to parse the xml
- * @param xml the xml to parse
- */
- public SimplePullParser(String xml) throws IOException, ParseException {
- this(new StringReader(xml));
- }
-
- /**
- * Constructs a new SimplePullParser to parse the xml
- * @param reader a reader containing the xml
- */
- public SimplePullParser(Reader reader) throws IOException, ParseException {
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(reader);
- moveToStartDocument(parser);
- mParser = parser;
- mCurrentStartTag = null;
- source = reader;
- } catch (XmlPullParserException e) {
- throw new ParseException(e);
- }
- }
-
- private static void moveToStartDocument(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- int eventType;
- eventType = parser.getEventType();
- if (eventType != XmlPullParser.START_DOCUMENT) {
- throw new XmlPullParserException("Not at start of response");
- }
- }
-
- /**
- * Enables logging to the provided log tag. A basic representation of the xml will be logged as
- * the xml is parsed. No logging is done unless this is called.
- *
- * @param logTag the log tag to use when logging
- */
- public void setLogTag(String logTag) {
- mLogTag = logTag;
- }
-
- /**
- * Returns the tag of the next element whose depth is parentDepth plus one
- * or null if there are no more such elements before the next start tag. When this returns,
- * getDepth() and all methods relating to attributes will refer to the element whose tag is
- * returned.
- *
- * @param parentDepth the depth of the parrent of the item to be returned
- * @param textBuilder if null then text blocks will be ignored. If
- * non-null then text blocks will be added to the builder and TEXT_TAG
- * will be returned when one is found
- * @return the next of the next child element's tag, TEXT_TAG if a text block is found, or null
- * if there are no more child elements or DATA blocks
- * @throws IOException propogated from the underlying parser
- * @throws ParseException if there was an error parsing the xml.
- */
- public String nextTagOrText(int parentDepth, StringBuilder textBuilder)
- throws IOException, ParseException {
- while (true) {
- int eventType = 0;
- try {
- eventType = mParser.next();
- } catch (XmlPullParserException e) {
- throw new ParseException(e);
- }
- int depth = mParser.getDepth();
- mCurrentStartTag = null;
-
- if (eventType == XmlPullParser.START_TAG && depth == parentDepth + 1) {
- mCurrentStartTag = mParser.getName();
- if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < depth; i++) sb.append(" ");
- sb.append("<").append(mParser.getName());
- int count = mParser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- sb.append(" ");
- sb.append(mParser.getAttributeName(i));
- sb.append("=\"");
- sb.append(mParser.getAttributeValue(i));
- sb.append("\"");
- }
- sb.append(">");
- Log.d(mLogTag, sb.toString());
- }
- return mParser.getName();
- }
-
- if (eventType == XmlPullParser.END_TAG && depth == parentDepth) {
- if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < depth; i++) sb.append(" ");
- sb.append("</>"); // Not quite valid xml but it gets the job done.
- Log.d(mLogTag, sb.toString());
- }
- return null;
- }
-
- if (eventType == XmlPullParser.END_DOCUMENT && parentDepth == 0) {
- // we could just rely on the caller calling close(), which it should, but try
- // to auto-close for clients that might have missed doing so.
- if (source != null) {
- source.close();
- source = null;
- }
- return null;
- }
-
- if (eventType == XmlPullParser.TEXT && depth == parentDepth) {
- if (textBuilder == null) {
- continue;
- }
- String text = mParser.getText();
- textBuilder.append(text);
- return TEXT_TAG;
- }
- }
- }
-
- /**
- * The same as nextTagOrTexxt(int, StringBuilder) but ignores text blocks.
- */
- public String nextTag(int parentDepth) throws IOException, ParseException {
- return nextTagOrText(parentDepth, null /* ignore text */);
- }
-
- /**
- * Returns the depth of the current element. The depth is 0 before the first
- * element has been returned, 1 after that, etc.
- *
- * @return the depth of the current element
- */
- public int getDepth() {
- return mParser.getDepth();
- }
-
- /**
- * Consumes the rest of the children, accumulating any text at this level into the builder.
- *
- * @param textBuilder the builder to contain any text
- * @throws IOException propogated from the XmlPullParser
- * @throws ParseException if there was an error parsing the xml.
- */
- public void readRemainingText(int parentDepth, StringBuilder textBuilder)
- throws IOException, ParseException {
- while (nextTagOrText(parentDepth, textBuilder) != null) {
- }
- }
-
- /**
- * Returns the number of attributes on the current element.
- *
- * @return the number of attributes on the current element
- */
- public int numAttributes() {
- return mParser.getAttributeCount();
- }
-
- /**
- * Returns the name of the nth attribute on the current element.
- *
- * @return the name of the nth attribute on the current element
- */
- public String getAttributeName(int i) {
- return mParser.getAttributeName(i);
- }
-
- /**
- * Returns the namespace of the nth attribute on the current element.
- *
- * @return the namespace of the nth attribute on the current element
- */
- public String getAttributeNamespace(int i) {
- return mParser.getAttributeNamespace(i);
- }
-
- /**
- * Returns the string value of the named attribute.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute
- * @param defaultValue the value to return if the attribute is not specified
- * @return the value of the attribute
- */
- public String getStringAttribute(
- String namespace, String name, String defaultValue) {
- String value = mParser.getAttributeValue(namespace, name);
- if (null == value) return defaultValue;
- return value;
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not present.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute @return the value of the attribute
- * @throws ParseException thrown if the attribute is missing
- */
- public String getStringAttribute(String namespace, String name) throws ParseException {
- String value = mParser.getAttributeValue(namespace, name);
- if (null == value) {
- throw new ParseException(
- "missing '" + name + "' attribute on '" + mCurrentStartTag + "' element");
- }
- return value;
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not a valid integer.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute
- * @param defaultValue the value to return if the attribute is not specified
- * @return the value of the attribute
- * @throws ParseException thrown if the attribute not a valid integer.
- */
- public int getIntAttribute(String namespace, String name, int defaultValue)
- throws ParseException {
- String value = mParser.getAttributeValue(namespace, name);
- if (null == value) return defaultValue;
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- throw new ParseException("Cannot parse '" + value + "' as an integer");
- }
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not present or is not a valid integer.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute @return the value of the attribute
- * @throws ParseException thrown if the attribute is missing or not a valid integer.
- */
- public int getIntAttribute(String namespace, String name)
- throws ParseException {
- String value = getStringAttribute(namespace, name);
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- throw new ParseException("Cannot parse '" + value + "' as an integer");
- }
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not a valid long.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute @return the value of the attribute
- * @throws ParseException thrown if the attribute is not a valid long.
- */
- public long getLongAttribute(String namespace, String name, long defaultValue)
- throws ParseException {
- String value = mParser.getAttributeValue(namespace, name);
- if (null == value) return defaultValue;
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException e) {
- throw new ParseException("Cannot parse '" + value + "' as a long");
- }
- }
-
- /**
- * Close this SimplePullParser and any underlying resources (e.g., its InputStream or
- * Reader source) used by this SimplePullParser.
- */
- public void close() {
- if (source != null) {
- try {
- source.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not present or is not a valid long.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute @return the value of the attribute
- * @throws ParseException thrown if the attribute is missing or not a valid long.
- */
- public long getLongAttribute(String namespace, String name)
- throws ParseException {
- String value = getStringAttribute(namespace, name);
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException e) {
- throw new ParseException("Cannot parse '" + value + "' as a long");
- }
- }
-
- public static final class ParseException extends Exception {
- public ParseException(String message) {
- super(message);
- }
-
- public ParseException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public ParseException(Throwable cause) {
- super(cause);
- }
- }
-}