diff options
249 files changed, 13288 insertions, 2030 deletions
@@ -85,6 +85,7 @@ LOCAL_SRC_FILES += \ core/java/android/app/IWallpaperService.aidl \ core/java/android/app/IWallpaperServiceCallback.aidl \ core/java/android/backup/IBackupManager.aidl \ + core/java/android/backup/IRestoreObserver.aidl \ core/java/android/backup/IRestoreSession.aidl \ core/java/android/bluetooth/IBluetoothA2dp.aidl \ core/java/android/bluetooth/IBluetoothDevice.aidl \ diff --git a/api/current.xml b/api/current.xml index 8b5f7ed..26902db 100644 --- a/api/current.xml +++ b/api/current.xml @@ -3496,72 +3496,6 @@ visibility="public" > </field> -<field name="donut_resource_pad23" - type="int" - transient="false" - volatile="false" - value="16843401" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="donut_resource_pad24" - type="int" - transient="false" - volatile="false" - value="16843400" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="donut_resource_pad25" - type="int" - transient="false" - volatile="false" - value="16843399" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="donut_resource_pad26" - type="int" - transient="false" - volatile="false" - value="16843398" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="donut_resource_pad27" - type="int" - transient="false" - volatile="false" - value="16843397" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="donut_resource_pad28" - type="int" - transient="false" - volatile="false" - value="16843396" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="donut_resource_pad3" type="int" transient="false" @@ -5388,6 +5322,17 @@ visibility="public" > </field> +<field name="largeScreens" + type="int" + transient="false" + volatile="false" + value="16843398" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="launchMode" type="int" transient="false" @@ -6268,6 +6213,17 @@ visibility="public" > </field> +<field name="normalScreens" + type="int" + transient="false" + volatile="false" + value="16843397" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="numColumns" type="int" transient="false" @@ -6818,6 +6774,17 @@ visibility="public" > </field> +<field name="progressBarStyleInverse" + type="int" + transient="false" + volatile="false" + value="16843399" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="progressBarStyleLarge" type="int" transient="false" @@ -6829,6 +6796,17 @@ visibility="public" > </field> +<field name="progressBarStyleLargeInverse" + type="int" + transient="false" + volatile="false" + value="16843401" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="progressBarStyleSmall" type="int" transient="false" @@ -6840,6 +6818,17 @@ visibility="public" > </field> +<field name="progressBarStyleSmallInverse" + type="int" + transient="false" + volatile="false" + value="16843400" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="progressBarStyleSmallTitle" type="int" transient="false" @@ -7654,6 +7643,17 @@ visibility="public" > </field> +<field name="smallScreens" + type="int" + transient="false" + volatile="false" + value="16843396" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="smoothScrollbar" type="int" transient="false" @@ -15255,6 +15255,17 @@ visibility="public" > </field> +<field name="Widget_ProgressBar_Inverse" + type="int" + transient="false" + volatile="false" + value="16973915" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="Widget_ProgressBar_Large" type="int" transient="false" @@ -15266,6 +15277,17 @@ visibility="public" > </field> +<field name="Widget_ProgressBar_Large_Inverse" + type="int" + transient="false" + volatile="false" + value="16973916" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="Widget_ProgressBar_Small" type="int" transient="false" @@ -15277,6 +15299,17 @@ visibility="public" > </field> +<field name="Widget_ProgressBar_Small_Inverse" + type="int" + transient="false" + volatile="false" + value="16973917" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="Widget_RatingBar" type="int" transient="false" @@ -15508,39 +15541,6 @@ visibility="public" > </field> -<field name="donut_resource_pad20" - type="int" - transient="false" - volatile="false" - value="16973917" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="donut_resource_pad21" - type="int" - transient="false" - volatile="false" - value="16973916" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="donut_resource_pad22" - type="int" - transient="false" - volatile="false" - value="16973915" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="donut_resource_pad3" type="int" transient="false" @@ -17313,6 +17313,17 @@ visibility="public" > </field> +<field name="ERROR_CODE_BAD_REQUEST" + type="int" + transient="false" + volatile="false" + value="8" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ERROR_CODE_CANCELED" type="int" transient="false" @@ -24301,6 +24312,19 @@ <parameter name="position" type="int"> </parameter> </method> +<method name="itemForPosition" + return="android.app.LauncherActivity.ListItem" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +<parameter name="position" type="int"> +</parameter> +</method> <method name="makeListItems" return="java.util.List<android.app.LauncherActivity.ListItem>" abstract="false" @@ -24409,6 +24433,16 @@ visibility="public" > </field> +<field name="resolveInfo" + type="android.content.pm.ResolveInfo" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="ListActivity" extends="android.app.Activity" @@ -38400,6 +38434,17 @@ visibility="public" > </field> +<field name="CONFIG_SCREEN_LAYOUT" + type="int" + transient="false" + volatile="false" + value="256" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="CONFIG_TOUCHSCREEN" type="int" transient="false" @@ -38868,6 +38913,28 @@ type="int" transient="false" volatile="false" + value="2048" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="FLAG_SUPPORTS_NORMAL_SCREENS" + type="int" + transient="false" + volatile="false" + value="1024" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="FLAG_SUPPORTS_SMALL_SCREENS" + type="int" + transient="false" + volatile="false" value="512" static="true" final="true" @@ -43080,6 +43147,50 @@ visibility="public" > </field> +<field name="SCREENLAYOUT_LARGE" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="SCREENLAYOUT_NORMAL" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="SCREENLAYOUT_SMALL" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="SCREENLAYOUT_UNDEFINED" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="TOUCHSCREEN_FINGER" type="int" transient="false" @@ -43214,6 +43325,16 @@ visibility="public" > </field> +<field name="screenLayout" + type="int" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="touchscreen" type="int" transient="false" @@ -60282,6 +60403,47 @@ </package> <package name="android.graphics.drawable" > +<interface name="Animatable" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="isRunning" + return="boolean" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="start" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="stop" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +</interface> <class name="AnimationDrawable" extends="android.graphics.drawable.DrawableContainer" abstract="false" @@ -60290,6 +60452,8 @@ deprecated="not deprecated" visibility="public" > +<implements name="android.graphics.drawable.Animatable"> +</implements> <implements name="java.lang.Runnable"> </implements> <constructor name="AnimationDrawable" @@ -126444,6 +126608,27 @@ <parameter name="flags" type="int"> </parameter> </method> +<method name="formatDateRange" + return="java.util.Formatter" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="formatter" type="java.util.Formatter"> +</parameter> +<parameter name="startMillis" type="long"> +</parameter> +<parameter name="endMillis" type="long"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> <method name="formatDateTime" return="java.lang.String" abstract="false" @@ -143174,6 +143359,19 @@ visibility="public" > </method> +<method name="buildDrawingCache" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="autoScale" type="boolean"> +</parameter> +</method> <method name="cancelLongPress" return="void" abstract="false" @@ -143750,6 +143948,19 @@ visibility="public" > </method> +<method name="getDrawingCache" + return="android.graphics.Bitmap" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="autoScale" type="boolean"> +</parameter> +</method> <method name="getDrawingCacheBackgroundColor" return="int" abstract="false" @@ -184646,7 +184857,7 @@ <method name="startMethodTracing" return="void" abstract="false" - native="true" + native="false" synchronized="false" static="true" final="false" diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp index 2f4a1c9..030d887 100644 --- a/camera/libcameraservice/CameraService.cpp +++ b/camera/libcameraservice/CameraService.cpp @@ -98,7 +98,7 @@ sp<ICamera> CameraService::connect(const sp<ICameraClient>& cameraClient) LOGD("CameraService::connect E (pid %d, client %p)", callingPid, cameraClient->asBinder().get()); - Mutex::Autolock lock(mLock); + Mutex::Autolock lock(mServiceLock); sp<Client> client; if (mClient != 0) { sp<Client> currentClient = mClient.promote(); @@ -125,13 +125,14 @@ sp<ICamera> CameraService::connect(const sp<ICameraClient>& cameraClient) LOGD("New client (pid %d) connecting, old reference was dangling...", callingPid); mClient.clear(); - if (mUsers > 0) { - LOGD("Still have client, rejected"); - return client; - } } } + if (mUsers > 0) { + LOGD("Still have client, rejected"); + return client; + } + // create a new Client object client = new Client(this, cameraClient, callingPid); mClient = client; @@ -152,7 +153,7 @@ void CameraService::removeClient(const sp<ICameraClient>& cameraClient) // destructor won't be called with the lock held. sp<Client> client; - Mutex::Autolock lock(mLock); + Mutex::Autolock lock(mServiceLock); if (mClient == 0) { // This happens when we have already disconnected. @@ -390,8 +391,6 @@ void CameraService::Client::disconnect() // from the user directly, or called by the destructor. if (mHardware == 0) return; - mCameraService->removeClient(mCameraClient); - LOGD("hardware teardown"); // Before destroying mHardware, we must make sure it's in the // idle state. @@ -402,6 +401,7 @@ void CameraService::Client::disconnect() mHardware->release(); mHardware.clear(); + mCameraService->removeClient(mCameraClient); mCameraService->decUsers(); LOGD("Client::disconnect() X (pid %d)", callingPid); @@ -410,11 +410,14 @@ void CameraService::Client::disconnect() // pass the buffered ISurface to the camera service status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) { - LOGD("setPreviewDisplay(%p) (pid %d)", surface.get(), getCallingPid()); + LOGD("setPreviewDisplay(%p) (pid %d)", + ((surface == NULL) ? NULL : surface.get()), getCallingPid()); Mutex::Autolock lock(mLock); status_t result = checkPid(); if (result != NO_ERROR) return result; + Mutex::Autolock surfaceLock(mSurfaceLock); + result = NO_ERROR; // asBinder() is safe on NULL (returns NULL) if (surface->asBinder() != mSurface->asBinder()) { if (mSurface != 0 && !mUseOverlay) { @@ -422,8 +425,17 @@ status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) mSurface->unregisterBuffers(); } mSurface = surface; + // If preview has been already started, set overlay or register preview + // buffers now. + if (mHardware->previewEnabled()) { + if (mUseOverlay) { + result = setOverlay(); + } else if (mSurface != 0) { + result = registerPreviewBuffers(); + } + } } - return NO_ERROR; + return result; } // set the preview callback flag to affect how the received frames from @@ -436,7 +448,7 @@ void CameraService::Client::setPreviewCallbackFlag(int callback_flag) mPreviewCallbackFlag = callback_flag; } -// start preview mode, must call setPreviewDisplay first +// start preview mode status_t CameraService::Client::startCameraMode(camera_mode mode) { int callingPid = getCallingPid(); @@ -456,16 +468,18 @@ status_t CameraService::Client::startCameraMode(camera_mode mode) return INVALID_OPERATION; } - if (mSurface == 0) { - LOGE("setPreviewDisplay must be called before startCameraMode!"); - return INVALID_OPERATION; - } - switch(mode) { case CAMERA_RECORDING_MODE: + if (mSurface == 0) { + LOGE("setPreviewDisplay must be called before startRecordingMode."); + return INVALID_OPERATION; + } return startRecordingMode(); default: // CAMERA_PREVIEW_MODE + if (mSurface == 0) { + LOGD("mSurface is not set yet."); + } return startPreviewMode(); } } @@ -498,6 +512,62 @@ status_t CameraService::Client::startRecordingMode() return ret; } +status_t CameraService::Client::setOverlay() +{ + LOGD("setOverlay"); + int w, h; + CameraParameters params(mHardware->getParameters()); + params.getPreviewSize(&w, &h); + + const char *format = params.getPreviewFormat(); + int fmt; + if (!strcmp(format, "yuv422i")) + fmt = OVERLAY_FORMAT_YCbCr_422_I; + else if (!strcmp(format, "rgb565")) + fmt = OVERLAY_FORMAT_RGB_565; + else { + LOGE("Invalid preview format for overlays"); + return -EINVAL; + } + + status_t ret = NO_ERROR; + if (mSurface != 0) { + sp<OverlayRef> ref = mSurface->createOverlay(w, h, fmt); + ret = mHardware->setOverlay(new Overlay(ref)); + } else { + ret = mHardware->setOverlay(NULL); + } + if (ret != NO_ERROR) { + LOGE("mHardware->setOverlay() failed with status %d\n", ret); + } + return ret; +} + +status_t CameraService::Client::registerPreviewBuffers() +{ + int w, h; + CameraParameters params(mHardware->getParameters()); + params.getPreviewSize(&w, &h); + + uint32_t transform = 0; + if (params.getOrientation() == + CameraParameters::CAMERA_ORIENTATION_PORTRAIT) { + LOGV("portrait mode"); + transform = ISurface::BufferHeap::ROT_90; + } + ISurface::BufferHeap buffers(w, h, w, h, + PIXEL_FORMAT_YCbCr_420_SP, + transform, + 0, + mHardware->getPreviewHeap()); + + status_t ret = mSurface->registerBuffers(buffers); + if (ret != NO_ERROR) { + LOGE("registerBuffers failed with status %d", ret); + } + return ret; +} + status_t CameraService::Client::startPreviewMode() { LOGD("startPreviewMode (pid %d)", getCallingPid()); @@ -511,55 +581,24 @@ status_t CameraService::Client::startPreviewMode() #if DEBUG_DUMP_PREVIEW_FRAME_TO_FILE debug_frame_cnt = 0; #endif - status_t ret = UNKNOWN_ERROR; - int w, h; - CameraParameters params(mHardware->getParameters()); - params.getPreviewSize(&w, &h); + status_t ret = NO_ERROR; if (mUseOverlay) { - const char *format = params.getPreviewFormat(); - int fmt; - LOGD("Use Overlays"); - if (!strcmp(format, "yuv422i")) - fmt = OVERLAY_FORMAT_YCbCr_422_I; - else if (!strcmp(format, "rgb565")) - fmt = OVERLAY_FORMAT_RGB_565; - else { - LOGE("Invalid preview format for overlays"); - return -EINVAL; - } - sp<OverlayRef> ref = mSurface->createOverlay(w, h, fmt); - ret = mHardware->setOverlay(new Overlay(ref)); - if (ret != NO_ERROR) { - LOGE("mHardware->setOverlay() failed with status %d\n", ret); - return ret; + // If preview display has been set, set overlay now. + if (mSurface != 0) { + ret = setOverlay(); } + if (ret != NO_ERROR) return ret; ret = mHardware->startPreview(NULL, mCameraService.get()); - if (ret != NO_ERROR) - LOGE("mHardware->startPreview() failed with status %d\n", ret); - } else { ret = mHardware->startPreview(previewCallback, mCameraService.get()); - if (ret == NO_ERROR) { - - mSurface->unregisterBuffers(); - - uint32_t transform = 0; - if (params.getOrientation() == - CameraParameters::CAMERA_ORIENTATION_PORTRAIT) { - LOGV("portrait mode"); - transform = ISurface::BufferHeap::ROT_90; - } - ISurface::BufferHeap buffers(w, h, w, h, - PIXEL_FORMAT_YCbCr_420_SP, - transform, - 0, - mHardware->getPreviewHeap()); - - mSurface->registerBuffers(buffers); - } else { - LOGE("mHardware->startPreview() failed with status %d", ret); + if (ret != NO_ERROR) return ret; + // If preview display has been set, register preview buffers now. + if (mSurface != 0) { + // Unregister here because the surface registered with raw heap. + mSurface->unregisterBuffers(); + ret = registerPreviewBuffers(); } } return ret; @@ -661,7 +700,7 @@ sp<CameraService::Client> CameraService::Client::getClientFromCookie(void* user) sp<Client> client = 0; CameraService *service = static_cast<CameraService*>(user); if (service != NULL) { - Mutex::Autolock ourLock(service->mLock); + Mutex::Autolock ourLock(service->mServiceLock); if (service->mClient != 0) { client = service->mClient.promote(); if (client == 0) { @@ -1104,7 +1143,7 @@ status_t CameraService::dump(int fd, const Vector<String16>& args) result.append(buffer); write(fd, result.string(), result.size()); } else { - AutoMutex lock(&mLock); + AutoMutex lock(&mServiceLock); if (mClient != 0) { sp<Client> currentClient = mClient.promote(); sprintf(buffer, "Client (%p) PID: %d\n", diff --git a/camera/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h index 729e539..0f07673 100644 --- a/camera/libcameraservice/CameraService.h +++ b/camera/libcameraservice/CameraService.h @@ -157,6 +157,8 @@ private: status_t startCameraMode(camera_mode mode); status_t startPreviewMode(); status_t startRecordingMode(); + status_t setOverlay(); + status_t registerPreviewBuffers(); // Ensures atomicity among the public methods mutable Mutex mLock; @@ -199,7 +201,7 @@ private: virtual void incUsers(); virtual void decUsers(); - mutable Mutex mLock; + mutable Mutex mServiceLock; wp<Client> mClient; #if DEBUG_HEAP_LEAKS diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 841e3df..c90b862 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -17,6 +17,7 @@ package com.android.commands.bmgr; import android.backup.IBackupManager; +import android.backup.IRestoreObserver; import android.backup.IRestoreSession; import android.backup.RestoreSet; import android.os.RemoteException; @@ -61,6 +62,16 @@ public final class Bmgr { String op = args[0]; mNextArg = 1; + if ("enabled".equals(op)) { + doEnabled(); + return; + } + + if ("enable".equals(op)) { + doEnable(); + return; + } + if ("run".equals(op)) { doRun(); return; @@ -90,6 +101,41 @@ public final class Bmgr { showUsage(); } + private String enableToString(boolean enabled) { + return enabled ? "enabled" : "disabled"; + } + + private void doEnabled() { + try { + boolean isEnabled = mBmgr.isBackupEnabled(); + System.out.println("Backup Manager currently " + + enableToString(isEnabled)); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + } + + private void doEnable() { + String arg = nextArg(); + if (arg == null) { + showUsage(); + return; + } + + try { + boolean enable = Boolean.parseBoolean(arg); + mBmgr.setBackupEnabled(enable); + System.out.println("Backup Manager now " + enableToString(enable)); + } catch (NumberFormatException e) { + showUsage(); + return; + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + } + private void doRun() { try { mBmgr.backupNow(); @@ -123,11 +169,14 @@ public final class Bmgr { private void doTransport() { try { - int which = Integer.parseInt(nextArg()); - int old = mBmgr.selectBackupTransport(which); - System.out.println("Selected transport " + which + " (formerly " + old + ")"); - } catch (NumberFormatException e) { - showUsage(); + String which = nextArg(); + String old = mBmgr.selectBackupTransport(which); + if (old == null) { + System.out.println("Unknown transport '" + which + + "' specified; no changes made."); + } else { + System.out.println("Selected transport " + which + " (formerly " + old + ")"); + } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -143,7 +192,7 @@ public final class Bmgr { // The rest of the 'list' options work with a restore session on the current transport try { - int curTransport = mBmgr.getCurrentTransport(); + String curTransport = mBmgr.getCurrentTransport(); mRestore = mBmgr.beginRestoreSession(curTransport); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); @@ -152,6 +201,8 @@ public final class Bmgr { if ("sets".equals(arg)) { doListRestoreSets(); + } else if ("transports".equals(arg)) { + doListTransports(); } mRestore.endRestoreSession(); @@ -162,6 +213,22 @@ public final class Bmgr { } private void doListTransports() { + try { + String current = mBmgr.getCurrentTransport(); + String[] transports = mBmgr.listAllTransports(); + if (transports == null || transports.length == 0) { + System.out.println("No transports available."); + return; + } + + for (String t : transports) { + String pad = (t.equals(current)) ? " * " : " "; + System.out.println(pad + t); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } } private void doListRestoreSets() { @@ -170,9 +237,7 @@ public final class Bmgr { if (sets == null || sets.length == 0) { System.out.println("No restore sets available"); } else { - for (RestoreSet s : sets) { - System.out.println(" " + s.token + " : " + s.name); - } + printRestoreSets(sets); } } catch (RemoteException e) { System.err.println(e.toString()); @@ -180,17 +245,45 @@ public final class Bmgr { } } + private void printRestoreSets(RestoreSet[] sets) { + for (RestoreSet s : sets) { + System.out.println(" " + s.token + " : " + s.name); + } + } + + class RestoreObserver extends IRestoreObserver.Stub { + boolean done; + public void restoreStarting(int numPackages) { + System.out.println("restoreStarting: " + numPackages + " packages"); + } + + public void onUpdate(int nowBeingRestored) { + System.out.println("onUpdate: " + nowBeingRestored); + } + + public void restoreFinished(int error) { + System.out.println("restoreFinished: " + error); + synchronized (this) { + done = true; + this.notify(); + } + } + } + private void doRestore() { - int token; + long token; try { - token = Integer.parseInt(nextArg()); + token = Long.parseLong(nextArg()); } catch (NumberFormatException e) { showUsage(); return; } + RestoreObserver observer = new RestoreObserver(); + try { - int curTransport = mBmgr.getCurrentTransport(); + boolean didRestore = false; + String curTransport = mBmgr.getCurrentTransport(); mRestore = mBmgr.beginRestoreSession(curTransport); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); @@ -200,15 +293,35 @@ public final class Bmgr { for (RestoreSet s : sets) { if (s.token == token) { System.out.println("Scheduling restore: " + s.name); - mRestore.performRestore(token); + mRestore.performRestore(token, observer); + didRestore = true; break; } } + if (!didRestore) { + if (sets == null || sets.length == 0) { + System.out.println("No available restore sets; no restore performed"); + } else { + System.out.println("No matching restore set token. Available sets:"); + printRestoreSets(sets); + } + } mRestore.endRestoreSession(); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } + + // now wait for it to be done + synchronized (observer) { + while (!observer.done) { + try { + observer.wait(); + } catch (InterruptedException ex) { + } + } + } + System.out.println("done"); } private String nextArg() { @@ -222,11 +335,43 @@ public final class Bmgr { private static void showUsage() { System.err.println("usage: bmgr [backup|restore|list|transport|run]"); - System.err.println(" bmgr backup [-f] package"); + System.err.println(" bmgr backup PACKAGE"); + System.err.println(" bmgr enable BOOL"); + System.err.println(" bmgr enabled"); + System.err.println(" bmgr list transports"); System.err.println(" bmgr list sets"); - System.err.println(" #bmgr list transports"); - System.err.println(" #bmgr transport which#"); - System.err.println(" bmgr restore token#"); + System.err.println(" bmgr transport WHICH"); + System.err.println(" bmgr restore TOKEN"); System.err.println(" bmgr run"); + System.err.println(""); + System.err.println("The 'backup' command schedules a backup pass for the named package."); + System.err.println("Note that the backup pass will effectively be a no-op if the package"); + System.err.println("does not actually have changed data to store."); + System.err.println(""); + System.err.println("The 'enable' command enables or disables the entire backup mechanism."); + System.err.println("If the argument is 'true' it will be enabled, otherwise it will be"); + System.err.println("disabled. When disabled, neither backup or restore operations will"); + System.err.println("be performed."); + System.err.println(""); + System.err.println("The 'enabled' command reports the current enabled/disabled state of"); + System.err.println("the backup mechanism."); + System.err.println(""); + System.err.println("The 'list transports' command reports the names of the backup transports"); + System.err.println("currently available on the device. These names can be passed as arguments"); + System.err.println("to the 'transport' command. The currently selected transport is indicated"); + System.err.println("with a '*' character."); + System.err.println(""); + System.err.println("The 'list sets' command reports the token and name of each restore set"); + System.err.println("available to the device via the current transport."); + System.err.println(""); + System.err.println("The 'transport' command designates the named transport as the currently"); + System.err.println("active one. This setting is persistent across reboots."); + System.err.println(""); + System.err.println("The 'restore' command initiates a restore operation, using the restore set"); + System.err.println("from the current transport whose token matches the argument."); + System.err.println(""); + System.err.println("The 'run' command causes any scheduled backup operation to be initiated"); + System.err.println("immediately, without the usual waiting period for batching together"); + System.err.println("data changes."); } -}
\ No newline at end of file +} diff --git a/core/java/android/accounts/Constants.java b/core/java/android/accounts/Constants.java index b383c61..fde920e 100644 --- a/core/java/android/accounts/Constants.java +++ b/core/java/android/accounts/Constants.java @@ -25,6 +25,7 @@ public class Constants { public static final int ERROR_CODE_INVALID_RESPONSE = 5; public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6; public static final int ERROR_CODE_BAD_ARGUMENTS = 7; + public static final int ERROR_CODE_BAD_REQUEST = 8; public static final String ACCOUNTS_KEY = "accounts"; public static final String AUTHENTICATOR_TYPES_KEY = "authenticator_types"; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 79588ea..62dc651 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3697,6 +3697,13 @@ public final class ActivityThread { */ Locale.setDefault(data.config.locale); + /* + * Update the system configuration since its preloaded and might not + * reflect configuration changes. The configuration object passed + * in AppBindData can be safely assumed to be up to date + */ + Resources.getSystem().updateConfiguration(mConfiguration, null); + data.info = getPackageInfoNoCheck(data.appInfo); if (data.debugMode != IApplicationThread.DEBUG_OFF) { diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java index e810775..0ac8a1e 100644 --- a/core/java/android/app/BackupAgent.java +++ b/core/java/android/app/BackupAgent.java @@ -67,7 +67,7 @@ public abstract class BackupAgent extends ContextWrapper { * here after writing the requested data to dataFd. */ public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, - ParcelFileDescriptor newState); + ParcelFileDescriptor newState) throws IOException; /** * The application is being restored from backup, and should replace any @@ -120,6 +120,9 @@ public abstract class BackupAgent extends ContextWrapper { BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor()); try { BackupAgent.this.onBackup(oldState, output, newState); + } catch (IOException ex) { + Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); + throw new RuntimeException(ex); } catch (RuntimeException ex) { Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); throw ex; diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java index 8d249da..accdda9 100644 --- a/core/java/android/app/LauncherActivity.java +++ b/core/java/android/app/LauncherActivity.java @@ -60,26 +60,20 @@ public abstract class LauncherActivity extends ListActivity { * An item in the list */ public static class ListItem { + public ResolveInfo resolveInfo; public CharSequence label; - //public CharSequence description; public Drawable icon; public String packageName; public String className; public Bundle extras; ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) { + this.resolveInfo = resolveInfo; label = resolveInfo.loadLabel(pm); if (label == null && resolveInfo.activityInfo != null) { label = resolveInfo.activityInfo.name; } - /* - if (resolveInfo.activityInfo != null && - resolveInfo.activityInfo.applicationInfo != null) { - description = resolveInfo.activityInfo.applicationInfo.loadDescription(pm); - } - */ - icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm)); packageName = resolveInfo.activityInfo.applicationInfo.packageName; className = resolveInfo.activityInfo.name; @@ -122,6 +116,14 @@ public abstract class LauncherActivity extends ListActivity { return intent; } + public ListItem itemForPosition(int position) { + if (mActivitiesList == null) { + return null; + } + + return mActivitiesList.get(position); + } + public int getCount() { return mActivitiesList != null ? mActivitiesList.size() : 0; } @@ -354,6 +356,16 @@ public abstract class LauncherActivity extends ListActivity { } /** + * Return the {@link ListItem} for a specific position in our + * {@link android.widget.ListView}. + * @param position The item to return + */ + protected ListItem itemForPosition(int position) { + ActivityAdapter adapter = (ActivityAdapter) mAdapter; + return adapter.itemForPosition(position); + } + + /** * Get the base intent to use when running * {@link PackageManager#queryIntentActivities(Intent, int)}. */ diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 6ddf50f..44d1eaa 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -33,8 +33,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; -import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Animatable; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; @@ -107,7 +107,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS private Button mGoButton; private ImageButton mVoiceButton; private View mSearchPlate; - private AnimationDrawable mWorkingSpinner; + private Drawable mWorkingSpinner; // interaction with searchable application private SearchableInfo mSearchable; @@ -188,7 +188,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS 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); - mWorkingSpinner = (AnimationDrawable) getContext().getResources(). + mWorkingSpinner = getContext().getResources(). getDrawable(com.android.internal.R.drawable.search_spinner); // attach listeners @@ -423,11 +423,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (working) { mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( null, null, mWorkingSpinner, null); - mWorkingSpinner.start(); + ((Animatable) mWorkingSpinner).start(); } else { mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( null, null, null, null); - mWorkingSpinner.stop(); + ((Animatable) mWorkingSpinner).stop(); } } @@ -601,7 +601,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchPlate.getPaddingBottom()); } else { PackageManager pm = getContext().getPackageManager(); - Drawable icon = null; + Drawable icon; try { ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0); icon = pm.getApplicationIcon(info.applicationInfo); diff --git a/core/java/android/backup/AbsoluteFileBackupHelper.java b/core/java/android/backup/AbsoluteFileBackupHelper.java new file mode 100644 index 0000000..ab24675 --- /dev/null +++ b/core/java/android/backup/AbsoluteFileBackupHelper.java @@ -0,0 +1,66 @@ +/* + * 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.backup; + +import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileDescriptor; + +/** + * Like FileBackupHelper, but takes absolute paths for the files instead of + * subpaths of getFilesDir() + * + * @hide + */ +public class AbsoluteFileBackupHelper extends FileBackupHelperBase implements BackupHelper { + private static final String TAG = "AbsoluteFileBackupHelper"; + + Context mContext; + String[] mFiles; + + public AbsoluteFileBackupHelper(Context context, String... files) { + super(context); + + mContext = context; + mFiles = files; + } + + /** + * Based on oldState, determine which of the files from the application's data directory + * need to be backed up, write them to the data stream, and fill in newState with the + * state as it exists now. + */ + public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) { + // use the file paths as the keys, too + performBackup_checked(oldState, data, newState, mFiles, mFiles); + } + + public void restoreEntity(BackupDataInputStream data) { + // TODO: turn this off before ship + Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size()); + String key = data.getKey(); + if (isKeyInList(key, mFiles)) { + File f = new File(key); + writeFile(f, data); + } + } +} + diff --git a/core/java/android/backup/BackupHelperAgent.java b/core/java/android/backup/BackupHelperAgent.java index 3720d50..5d0c4a2 100644 --- a/core/java/android/backup/BackupHelperAgent.java +++ b/core/java/android/backup/BackupHelperAgent.java @@ -34,7 +34,7 @@ public class BackupHelperAgent extends BackupAgent { @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, - ParcelFileDescriptor newState) { + ParcelFileDescriptor newState) throws IOException { mDispatcher.performBackup(oldState, data, newState); } diff --git a/core/java/android/backup/BackupHelperDispatcher.java b/core/java/android/backup/BackupHelperDispatcher.java index b25c3e3..6ccb83e 100644 --- a/core/java/android/backup/BackupHelperDispatcher.java +++ b/core/java/android/backup/BackupHelperDispatcher.java @@ -19,7 +19,10 @@ package android.backup; import android.os.ParcelFileDescriptor; import android.util.Log; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; +import java.io.FileDescriptor; import java.util.TreeMap; import java.util.Map; @@ -27,6 +30,11 @@ import java.util.Map; public class BackupHelperDispatcher { private static final String TAG = "BackupHelperDispatcher"; + private static class Header { + int chunkSize; // not including the header + String keyPrefix; + } + TreeMap<String,BackupHelper> mHelpers = new TreeMap<String,BackupHelper>(); public BackupHelperDispatcher() { @@ -36,13 +44,63 @@ public class BackupHelperDispatcher { mHelpers.put(keyPrefix, helper); } - /** TODO: Make this save and restore the key prefix. */ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, - ParcelFileDescriptor newState) { - // Write out the state files -- mHelpers is a TreeMap, so the order is well defined. - for (Map.Entry<String,BackupHelper> entry: mHelpers.entrySet()) { - data.setKeyPrefix(entry.getKey()); - entry.getValue().performBackup(oldState, data, newState); + ParcelFileDescriptor newState) throws IOException { + // First, do the helpers that we've already done, since they're already in the state + // file. + int err; + Header header = new Header(); + TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone(); + FileDescriptor oldStateFD = null; + FileDescriptor newStateFD = newState.getFileDescriptor(); + + if (oldState != null) { + oldStateFD = oldState.getFileDescriptor(); + while ((err = readHeader_native(header, oldStateFD)) >= 0) { + if (err == 0) { + BackupHelper helper = helpers.get(header.keyPrefix); + Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper); + if (helper != null) { + doOneBackup(oldState, data, newState, header, helper); + helpers.remove(header.keyPrefix); + } else { + skipChunk_native(oldStateFD, header.chunkSize); + } + } + } + } + + // Then go through and do the rest that we haven't done. + for (Map.Entry<String,BackupHelper> entry: helpers.entrySet()) { + header.keyPrefix = entry.getKey(); + Log.d(TAG, "handling new helper '" + header.keyPrefix + "'"); + BackupHelper helper = entry.getValue(); + doOneBackup(oldState, data, newState, header, helper); + } + } + + private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState, Header header, BackupHelper helper) + throws IOException { + int err; + FileDescriptor newStateFD = newState.getFileDescriptor(); + + // allocate space for the header in the file + int pos = allocateHeader_native(header, newStateFD); + if (pos < 0) { + throw new IOException("allocateHeader_native failed (error " + pos + ")"); + } + + data.setKeyPrefix(header.keyPrefix); + + // do the backup + helper.performBackup(oldState, data, newState); + + // fill in the header (seeking back to pos). The file pointer will be returned to + // where it was at the end of performBackup. Header.chunkSize will not be filled in. + err = writeHeader_native(header, newStateFD, pos); + if (err != 0) { + throw new IOException("writeHeader_native failed (error " + err + ")"); } } @@ -83,5 +141,11 @@ public class BackupHelperDispatcher { helper.writeRestoreSnapshot(newState); } } + + private static native int readHeader_native(Header h, FileDescriptor fd); + private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip); + + private static native int allocateHeader_native(Header h, FileDescriptor fd); + private static native int writeHeader_native(Header h, FileDescriptor fd, int pos); } diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java index 8df7eae..5b4ac0d 100644 --- a/core/java/android/backup/BackupManager.java +++ b/core/java/android/backup/BackupManager.java @@ -68,9 +68,11 @@ public class BackupManager { * {@link android.app.BackupAgent} subclass will be scheduled when you call this method. */ public void dataChanged() { - try { - mService.dataChanged(mContext.getPackageName()); - } catch (RemoteException e) { + if (mService != null) { + try { + mService.dataChanged(mContext.getPackageName()); + } catch (RemoteException e) { + } } } @@ -81,11 +83,13 @@ public class BackupManager { * * {@hide} */ - public IRestoreSession beginRestoreSession(int transportID) { + public IRestoreSession beginRestoreSession(String transport) { IRestoreSession binder = null; - try { - binder = mService.beginRestoreSession(transportID); - } catch (RemoteException e) { + if (mService != null) { + try { + binder = mService.beginRestoreSession(transport); + } catch (RemoteException e) { + } } return binder; } diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl index d6283d0..1f11762 100644 --- a/core/java/android/backup/IBackupManager.aidl +++ b/core/java/android/backup/IBackupManager.aidl @@ -48,6 +48,24 @@ interface IBackupManager { void agentDisconnected(String packageName); /** + * Enable/disable the backup service entirely. When disabled, no backup + * or restore operations will take place. Data-changed notifications will + * still be observed and collected, however, so that changes made while the + * mechanism was disabled will still be backed up properly if it is enabled + * at some point in the future. + * + * <p>Callers must hold the android.permission.BACKUP permission to use this method. + */ + void setBackupEnabled(boolean isEnabled); + + /** + * Report whether the backup mechanism is currently enabled. + * + * <p>Callers must hold the android.permission.BACKUP permission to use this method. + */ + boolean isBackupEnabled(); + + /** * Schedule an immediate backup attempt for all pending updates. This is * primarily intended for transports to use when they detect a suitable * opportunity for doing a backup pass. If there are no pending updates to @@ -63,23 +81,32 @@ interface IBackupManager { * Identify the currently selected transport. Callers must hold the * android.permission.BACKUP permission to use this method. */ - int getCurrentTransport(); + String getCurrentTransport(); + + /** + * Request a list of all available backup transports' names. Callers must + * hold the android.permission.BACKUP permission to use this method. + */ + String[] listAllTransports(); /** - * Specify a default backup transport. Callers must hold the + * Specify the current backup transport. Callers must hold the * android.permission.BACKUP permission to use this method. * - * @param transportID The ID of the transport to select. This should be one + * @param transport The name of the transport to select. This should be one * of {@link BackupManager.TRANSPORT_GOOGLE} or {@link BackupManager.TRANSPORT_ADB}. - * @return The ID of the previously selected transport. + * @return The name of the previously selected transport. If the given transport + * name is not one of the currently available transports, no change is made to + * the current transport setting and the method returns null. */ - int selectBackupTransport(int transportID); + String selectBackupTransport(String transport); /** * Begin a restore session with the given transport (which may differ from the * currently-active backup transport). * + * @param transport The name of the transport to use for the restore operation. * @return An interface to the restore session, or null on error. */ - IRestoreSession beginRestoreSession(int transportID); + IRestoreSession beginRestoreSession(String transportID); } diff --git a/core/java/android/backup/IRestoreObserver.aidl b/core/java/android/backup/IRestoreObserver.aidl new file mode 100644 index 0000000..59e59fc --- /dev/null +++ b/core/java/android/backup/IRestoreObserver.aidl @@ -0,0 +1,50 @@ +/* + * 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.backup; + +/** + * Callback class for receiving progress reports during a restore operation. + * + * @hide + */ +interface IRestoreObserver { + /** + * The restore operation has begun. + * + * @param numPackages The total number of packages being processed in + * this restore operation. + */ + void restoreStarting(int numPackages); + + /** + * An indication of which package is being restored currently, out of the + * total number provided in the restoreStarting() callback. This method + * is not guaranteed to be called. + * + * @param nowBeingRestored The index, between 1 and the numPackages parameter + * to the restoreStarting() callback, of the package now being restored. + */ + void onUpdate(int nowBeingRestored); + + /** + * The restore operation has completed. + * + * @param error Zero on success; a nonzero error code if the restore operation + * as a whole failed. + */ + void restoreFinished(int error); +} diff --git a/core/java/android/backup/IRestoreSession.aidl b/core/java/android/backup/IRestoreSession.aidl index 6bca865..2a1fbc1 100644 --- a/core/java/android/backup/IRestoreSession.aidl +++ b/core/java/android/backup/IRestoreSession.aidl @@ -17,6 +17,7 @@ package android.backup; import android.backup.RestoreSet; +import android.backup.IRestoreObserver; /** * Binder interface used by clients who wish to manage a restore operation. Every @@ -41,8 +42,10 @@ interface IRestoreSession { * * @param token The token from {@link getAvailableRestoreSets()} corresponding to * the restore set that should be used. + * @param observer If non-null, this binder points to an object that will receive + * progress callbacks during the restore operation. */ - int performRestore(int token); + int performRestore(long token, IRestoreObserver observer); /** * End this restore session. After this method is called, the IRestoreSession binder diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java index f492629..4a7b399 100644 --- a/core/java/android/backup/SharedPreferencesBackupHelper.java +++ b/core/java/android/backup/SharedPreferencesBackupHelper.java @@ -30,7 +30,7 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen private Context mContext; private String[] mPrefGroups; - public SharedPreferencesBackupHelper(Context context, String[] prefGroups) { + public SharedPreferencesBackupHelper(Context context, String... prefGroups) { super(context); mContext = context; diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 85d877a..27783ef 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -235,6 +235,12 @@ public class ActivityInfo extends ComponentInfo public static final int CONFIG_ORIENTATION = 0x0080; /** * Bit in {@link #configChanges} that indicates that the activity + * can itself handle changes to the screen layout. Set from the + * {@link android.R.attr#configChanges} attribute. + */ + public static final int CONFIG_SCREEN_LAYOUT = 0x0100; + /** + * Bit in {@link #configChanges} that indicates that the activity * can itself handle changes to the font scaling factor. Set from the * {@link android.R.attr#configChanges} attribute. This is * not a core resource configutation, but a higher-level value, so its @@ -248,8 +254,8 @@ public class ActivityInfo extends ComponentInfo * Contains any combination of {@link #CONFIG_FONT_SCALE}, * {@link #CONFIG_MCC}, {@link #CONFIG_MNC}, * {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN}, - * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION}, and - * {@link #CONFIG_ORIENTATION}. Set from the + * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION}, + * {@link #CONFIG_ORIENTATION}, and {@link #CONFIG_SCREEN_LAYOUT}. Set from the * {@link android.R.attr#configChanges} attribute. */ public int configChanges; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 2a2cf93..bcf95b6 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -138,10 +138,27 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * Value for {@link #flags}: true when the application's window can be - * expanded over default window size in target density (320x480 for - * 1.0 density, 480x720 for 1.5 density etc) + * reduced in size for smaller screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_smallScreens + * android:smallScreens}. */ - public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<9; + public static final int FLAG_SUPPORTS_SMALL_SCREENS = 1<<9; + + /** + * Value for {@link #flags}: true when the application's window can be + * displayed on normal screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_normalScreens + * android:normalScreens}. + */ + public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10; + + /** + * Value for {@link #flags}: true when the application's window can be + * increased in size for larger screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_largeScreens + * android:smallScreens}. + */ + public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11; /** * Value for {@link #flags}: this is false if the application has set @@ -149,7 +166,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * * {@hide} */ - public static final int FLAG_ALLOW_BACKUP = 1<<10; + public static final int FLAG_ALLOW_BACKUP = 1<<12; /** * Indicates that the application supports any densities; @@ -164,7 +181,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and * {@link #FLAG_ALLOW_TASK_REPARENTING} * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP}, - * {@link #FLAG_TEST_ONLY}. + * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS}, + * {@link #FLAG_SUPPORTS_NORMAL_SCREENS}, + * {@link #FLAG_SUPPORTS_LARGE_SCREENS}. */ public int flags = 0; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index ab9518e..558b0c3 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -668,6 +668,11 @@ public class PackageParser { } sa.recycle(); + // Resource boolean are -1, so 1 means we don't know the value. + int supportsSmallScreens = 1; + int supportsNormalScreens = 1; + int supportsLargeScreens = 1; + int outerDepth = parser.getDepth(); while ((type=parser.next()) != parser.END_DOCUMENT && (type != parser.END_TAG || parser.getDepth() > outerDepth)) { @@ -876,8 +881,24 @@ public class PackageParser { XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals("expandable")) { - pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; + } else if (tagName.equals("supports-screens")) { + sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestSupportsScreens); + + // This is a trick to get a boolean and still able to detect + // if a value was actually set. + supportsSmallScreens = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestSupportsScreens_smallScreens, + supportsSmallScreens); + supportsNormalScreens = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestSupportsScreens_normalScreens, + supportsNormalScreens); + supportsLargeScreens = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens, + supportsLargeScreens); + + sa.recycle(); + XmlUtils.skipCurrentTag(parser); } else { Log.w(TAG, "Bad element under <manifest>: " @@ -910,7 +931,20 @@ public class PackageParser { pkg.usesLibraryFiles = new String[pkg.usesLibraries.size()]; pkg.usesLibraries.toArray(pkg.usesLibraryFiles); } - // TODO: enable all density & expandable if target sdk is higher than donut + + if (supportsSmallScreens < 0 || (supportsSmallScreens > 0 + && pkg.applicationInfo.targetSdkVersion + >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS; + } + if (supportsNormalScreens != 0) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS; + } + if (supportsLargeScreens < 0 || (supportsLargeScreens > 0 + && pkg.applicationInfo.targetSdkVersion + >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; + } int size = pkg.supportsDensityList.size(); if (size > 0) { diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 1c91736..5c7b01f 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -601,7 +601,7 @@ public final class AssetManager { public native final void setConfiguration(int mcc, int mnc, String locale, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, - int majorVersion); + int screenLayout, int majorVersion); /** * Retrieve the resource identifier for the given resource name. diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 179b9bd..4e6fe07 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -65,7 +65,7 @@ public class CompatibilityInfo { /** * A compatibility flags */ - private int compatibilityFlags; + private int mCompatibilityFlags; /** * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f) @@ -101,7 +101,11 @@ public class CompatibilityInfo { */ public final float applicationInvertedScale; - + /** + * The flags from ApplicationInfo. + */ + public final int appFlags; + /** * Window size in Compatibility Mode, in real pixels. This is updated by * {@link DisplayMetrics#updateMetrics}. @@ -117,8 +121,10 @@ public class CompatibilityInfo { private int mXOffset; public CompatibilityInfo(ApplicationInfo appInfo) { + appFlags = appInfo.flags; + if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { - compatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE; + mCompatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE; } float packageDensityScale = -1.0f; @@ -149,13 +155,16 @@ public class CompatibilityInfo { } applicationInvertedScale = 1.0f / applicationScale; if (applicationScale != 1.0f) { - compatibilityFlags |= SCALING_REQUIRED; + mCompatibilityFlags |= SCALING_REQUIRED; } } private CompatibilityInfo() { + appFlags = ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS + | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS + | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; applicationScale = applicationInvertedScale = 1.0f; - compatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE; + mCompatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE; } /** @@ -175,9 +184,9 @@ public class CompatibilityInfo { */ public void setExpandable(boolean expandable) { if (expandable) { - compatibilityFlags |= CompatibilityInfo.EXPANDABLE; + mCompatibilityFlags |= CompatibilityInfo.EXPANDABLE; } else { - compatibilityFlags &= ~CompatibilityInfo.EXPANDABLE; + mCompatibilityFlags &= ~CompatibilityInfo.EXPANDABLE; } } @@ -185,20 +194,20 @@ public class CompatibilityInfo { * @return true if the application is configured to be expandable. */ public boolean isConfiguredExpandable() { - return (compatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0; + return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0; } /** * @return true if the scaling is required */ public boolean isScalingRequired() { - return (compatibilityFlags & SCALING_REQUIRED) != 0; + return (mCompatibilityFlags & SCALING_REQUIRED) != 0; } @Override public String toString() { return "CompatibilityInfo{scale=" + applicationScale + - ", compatibility flag=" + compatibilityFlags + "}"; + ", compatibility flag=" + mCompatibilityFlags + "}"; } /** @@ -222,13 +231,13 @@ public class CompatibilityInfo { * @param params the window's parameter */ public Translator getTranslator(WindowManager.LayoutParams params) { - if ( (compatibilityFlags & CompatibilityInfo.SCALING_EXPANDABLE_MASK) + if ( (mCompatibilityFlags & CompatibilityInfo.SCALING_EXPANDABLE_MASK) == CompatibilityInfo.EXPANDABLE) { if (DBG) Log.d(TAG, "no translation required"); return null; } - if ((compatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) { + if ((mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) { if ((params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) { if (DBG) Log.d(TAG, "translation for surface view selected"); return new Translator(X_SHIFT_WINDOW, false, 1.0f, 1.0f); diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index bb3486c..577aa60 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -116,6 +116,18 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public int orientation; + public static final int SCREENLAYOUT_UNDEFINED = 0; + public static final int SCREENLAYOUT_SMALL = 1; + public static final int SCREENLAYOUT_NORMAL = 2; + public static final int SCREENLAYOUT_LARGE = 3; + + /** + * Overall layout of the screen. May be one of + * {@link #SCREENLAYOUT_SMALL}, {@link #SCREENLAYOUT_NORMAL}, + * or {@link #SCREENLAYOUT_LARGE}. + */ + public int screenLayout; + /** * Construct an invalid Configuration. You must call {@link #setToDefaults} * for this object to be valid. {@more} @@ -141,6 +153,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration hardKeyboardHidden = o.hardKeyboardHidden; navigation = o.navigation; orientation = o.orientation; + screenLayout = o.screenLayout; } public String toString() { @@ -165,6 +178,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration sb.append(navigation); sb.append(" orien="); sb.append(orientation); + sb.append(" layout="); + sb.append(screenLayout); sb.append('}'); return sb.toString(); } @@ -183,6 +198,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration hardKeyboardHidden = HARDKEYBOARDHIDDEN_UNDEFINED; navigation = NAVIGATION_UNDEFINED; orientation = ORIENTATION_UNDEFINED; + screenLayout = SCREENLAYOUT_UNDEFINED; } /** {@hide} */ @@ -253,6 +269,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration changed |= ActivityInfo.CONFIG_ORIENTATION; orientation = delta.orientation; } + if (delta.screenLayout != SCREENLAYOUT_UNDEFINED + && screenLayout != delta.screenLayout) { + changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; + screenLayout = delta.screenLayout; + } return changed; } @@ -276,9 +297,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration * {@link android.content.pm.ActivityInfo#CONFIG_KEYBOARD * PackageManager.ActivityInfo.CONFIG_KEYBOARD}, * {@link android.content.pm.ActivityInfo#CONFIG_NAVIGATION - * PackageManager.ActivityInfo.CONFIG_NAVIGATION}, or + * PackageManager.ActivityInfo.CONFIG_NAVIGATION}, * {@link android.content.pm.ActivityInfo#CONFIG_ORIENTATION - * PackageManager.ActivityInfo.CONFIG_ORIENTATION}. + * PackageManager.ActivityInfo.CONFIG_ORIENTATION}, or + * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_LAYOUT + * PackageManager.ActivityInfo.CONFIG_SCREEN_LAYOUT}. */ public int diff(Configuration delta) { int changed = 0; @@ -319,6 +342,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration && orientation != delta.orientation) { changed |= ActivityInfo.CONFIG_ORIENTATION; } + if (delta.screenLayout != SCREENLAYOUT_UNDEFINED + && screenLayout != delta.screenLayout) { + changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; + } return changed; } @@ -368,6 +395,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(hardKeyboardHidden); dest.writeInt(navigation); dest.writeInt(orientation); + dest.writeInt(screenLayout); } public static final Parcelable.Creator<Configuration> CREATOR @@ -399,6 +427,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration hardKeyboardHidden = source.readInt(); navigation = source.readInt(); orientation = source.readInt(); + screenLayout = source.readInt(); } public int compareTo(Configuration that) { @@ -428,6 +457,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration n = this.navigation - that.navigation; if (n != 0) return n; n = this.orientation - that.orientation; + if (n != 0) return n; + n = this.screenLayout - that.screenLayout; //if (n != 0) return n; return n; } @@ -450,6 +481,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration return ((int)this.fontScale) + this.mcc + this.mnc + this.locale.hashCode() + this.touchscreen + this.keyboard + this.keyboardHidden + this.hardKeyboardHidden - + this.navigation + this.orientation; + + this.navigation + this.orientation + this.screenLayout; } } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index cb9d46e..d7512bb 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1267,7 +1267,8 @@ public class Resources { } if (metrics != null) { mMetrics.setTo(metrics); - mMetrics.updateMetrics(mCompatibilityInfo, mConfiguration.orientation); + mMetrics.updateMetrics(mCompatibilityInfo, + mConfiguration.orientation, mConfiguration.screenLayout); } mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; @@ -1299,7 +1300,7 @@ public class Resources { mConfiguration.touchscreen, (int)(mMetrics.density*160), mConfiguration.keyboard, keyboardHidden, mConfiguration.navigation, width, height, - sSdkVersion); + mConfiguration.screenLayout, sSdkVersion); int N = mDrawableCache.size(); if (DEBUG_CONFIG) { Log.d(TAG, "Cleaning up drawables config changes: 0x" diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index ca579b6..3ce951f 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -39,13 +39,16 @@ import android.os.Message; public class Camera { private static final String TAG = "Camera"; - // These match the enum in libs/android_runtime/android_hardware_Camera.cpp - private static final int SHUTTER_CALLBACK = 0; - private static final int RAW_PICTURE_CALLBACK = 1; - private static final int JPEG_PICTURE_CALLBACK = 2; - private static final int PREVIEW_CALLBACK = 3; - private static final int AUTOFOCUS_CALLBACK = 4; - private static final int ERROR_CALLBACK = 5; + // These match the enums in frameworks/base/include/ui/Camera.h + private static final int CAMERA_MSG_ERROR = 0; + private static final int CAMERA_MSG_SHUTTER = 1; + private static final int CAMERA_MSG_FOCUS = 2; + private static final int CAMERA_MSG_ZOOM = 3; + private static final int CAMERA_MSG_PREVIEW_FRAME = 4; + private static final int CAMERA_MSG_VIDEO_FRAME = 5; + private static final int CAMERA_MSG_POSTVIEW_FRAME = 6; + private static final int CAMERA_MSG_RAW_IMAGE = 7; + private static final int CAMERA_MSG_COMPRESSED_IMAGE = 8; private int mNativeContext; // accessed by native methods private EventHandler mEventHandler; @@ -152,7 +155,11 @@ public class Camera { * @throws IOException if the method fails. */ public final void setPreviewDisplay(SurfaceHolder holder) throws IOException { - setPreviewDisplay(holder.getSurface()); + if (holder != null) { + setPreviewDisplay(holder.getSurface()); + } else { + setPreviewDisplay((Surface)null); + } } private native final void setPreviewDisplay(Surface surface); @@ -231,22 +238,23 @@ public class Camera { @Override public void handleMessage(Message msg) { switch(msg.what) { - case SHUTTER_CALLBACK: + case CAMERA_MSG_SHUTTER: if (mShutterCallback != null) { mShutterCallback.onShutter(); } return; - case RAW_PICTURE_CALLBACK: + + case CAMERA_MSG_RAW_IMAGE: if (mRawImageCallback != null) mRawImageCallback.onPictureTaken((byte[])msg.obj, mCamera); return; - case JPEG_PICTURE_CALLBACK: + case CAMERA_MSG_COMPRESSED_IMAGE: if (mJpegCallback != null) mJpegCallback.onPictureTaken((byte[])msg.obj, mCamera); return; - case PREVIEW_CALLBACK: + case CAMERA_MSG_PREVIEW_FRAME: if (mPreviewCallback != null) { mPreviewCallback.onPreviewFrame((byte[])msg.obj, mCamera); if (mOneShot) { @@ -255,12 +263,12 @@ public class Camera { } return; - case AUTOFOCUS_CALLBACK: + case CAMERA_MSG_FOCUS: if (mAutoFocusCallback != null) mAutoFocusCallback.onAutoFocus(msg.arg1 == 0 ? false : true, mCamera); return; - case ERROR_CALLBACK: + case CAMERA_MSG_ERROR : Log.e(TAG, "Error " + msg.arg1); if (mErrorCallback != null) mErrorCallback.onError(msg.arg1, mCamera); diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java index c4ee5b0..6a97951 100644 --- a/core/java/android/net/http/RequestHandle.java +++ b/core/java/android/net/http/RequestHandle.java @@ -159,11 +159,11 @@ public class RequestHandle { e.printStackTrace(); } - // update the "cookie" header based on the redirected url - mHeaders.remove("cookie"); + // update the "Cookie" header based on the redirected url + mHeaders.remove("Cookie"); String cookie = CookieManager.getInstance().getCookie(mUri); if (cookie != null && cookie.length() > 0) { - mHeaders.put("cookie", cookie); + mHeaders.put("Cookie", cookie); } if ((statusCode == 302 || statusCode == 303) && mMethod.equals("POST")) { diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 6c13582..abfb274 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -127,12 +127,12 @@ import java.util.concurrent.atomic.AtomicInteger; public abstract class AsyncTask<Params, Progress, Result> { private static final String LOG_TAG = "AsyncTask"; - private static final int CORE_POOL_SIZE = 1; - private static final int MAXIMUM_POOL_SIZE = 10; + private static final int CORE_POOL_SIZE = 5; + private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 10; private static final BlockingQueue<Runnable> sWorkQueue = - new LinkedBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE); + new LinkedBlockingQueue<Runnable>(10); private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 333c7cb..1214abc 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -573,7 +573,21 @@ public class Process { * directly to a gid. */ public static final native int getGidForName(String name); - + + /** + * Returns a uid for a currently running process. + * @param pid the process id + * @return the uid of the process, or -1 if the process is not running. + * @hide pending API council review + */ + public static final int getUidForPid(int pid) { + String[] procStatusLabels = { "Uid:" }; + long[] procStatusValues = new long[1]; + procStatusValues[0] = -1; + Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues); + return (int) procStatusValues[0]; + } + /** * Set the priority of a thread, based on Linux priorities. * diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java index 6ea2528..95e5432 100644 --- a/core/java/android/preference/PreferenceScreen.java +++ b/core/java/android/preference/PreferenceScreen.java @@ -150,7 +150,7 @@ public final class PreferenceScreen extends PreferenceGroup implements AdapterVi // Set the title bar if title is available, else no title bar final CharSequence title = getTitle(); - Dialog dialog = mDialog = new Dialog(context, !TextUtils.isEmpty(title) + Dialog dialog = mDialog = new Dialog(context, TextUtils.isEmpty(title) ? com.android.internal.R.style.Theme_NoTitleBar : com.android.internal.R.style.Theme); dialog.setContentView(listView); diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java index 2490b8a..b95e4e1 100644 --- a/core/java/android/provider/Browser.java +++ b/core/java/android/provider/Browser.java @@ -91,6 +91,17 @@ public class Browser { */ public static final String EXTRA_APPEND_LOCATION = "com.android.browser.append_location"; + /** + * The name of the extra data in the VIEW intent. The data is in the format of + * a byte array. + * <p> + * Any value sent here will be passed in the http request to the provided url as post data. + * <p> + * pending api approval + * @hide + */ + public static final String EXTRA_POST_DATA = "com.android.browser.post_data"; + /* if you change column order you must also change indices below */ public static final String[] HISTORY_PROJECTION = new String[] { diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 48dc3ae..5cc3315 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -16,6 +16,9 @@ package android.provider; +import java.util.Arrays; +import java.util.List; + import android.content.Intent; import android.graphics.BitmapFactory; import android.net.Uri; @@ -127,6 +130,19 @@ public final class ContactsContract { public static final String STARRED = "starred"; /** + * A custom ringtone associated with a person. Not always present. + * <P>Type: TEXT (URI to the ringtone)</P> + */ + public static final String CUSTOM_RINGTONE = "custom_ringtone"; + + /** + * Whether the person should always be sent to voicemail. Not always + * present. + * <P>Type: INTEGER (0 for false, 1 for true)</P> + */ + public static final String SEND_TO_VOICEMAIL = "send_to_voicemail"; + + /** * Reference to the row in the data table holding the primary phone number. * <P>Type: INTEGER REFERENCES data(_id)</P> */ @@ -143,12 +159,6 @@ public final class ContactsContract { * <P>Type: INTEGER REFERENCES data(_id)</P> */ public static final String PHOTO_ID = "photo_id"; - - /** - * Reference to a row containing custom ringtone and send to voicemail information. - * <P>Type: INTEGER REFERENCES data(_id)</P> - */ - public static final String CUSTOM_RINGTONE_ID = "custom_ringtone_id"; } /** @@ -261,6 +271,19 @@ public final class ContactsContract { private Contacts() {} /** + * The package name that owns this contact and all of its details. This + * package has control over the {@link #IS_RESTRICTED} flag, and can + * grant {@link RestrictionExceptions} to other packages. + */ + public static final String PACKAGE = "package"; + + /** + * Flag indicating that this data entry has been restricted by the owner + * {@link #PACKAGE}. + */ + public static final String IS_RESTRICTED = "is_restricted"; + + /** * A reference to the {@link Accounts#_ID} that this data belongs to. */ public static final String ACCOUNTS_ID = "accounts_id"; @@ -332,11 +355,6 @@ public final class ContactsContract { private interface DataColumns { /** - * The package name that defines this type of data. - */ - public static final String PACKAGE = "package"; - - /** * The mime-type of the item represented by this row. */ public static final String MIMETYPE = "mimetype"; @@ -361,12 +379,6 @@ public final class ContactsContract { public static final String IS_SUPER_PRIMARY = "is_super_primary"; /** - * Flag indicating that this data entry has been restricted by the owner - * {@link #PACKAGE}. - */ - public static final String IS_RESTRICTED = "is_restricted"; - - /** * The version of this data record. This is a read-only value. The data column is * guaranteed to not change without the version going up. This value is monotonically * increasing. @@ -519,6 +531,18 @@ public final class ContactsContract { } /** + * Returns the precedence of the status code the higher number being the higher precedence. + * + * @param status The status code. + * @return An integer representing the precedence, 0 being the lowest. + */ + public static final int getPresencePrecedence(int status) { + // Keep this function here incase we want to enforce a different precedence than the + // natural order of the status constants. + return status; + } + + /** * The MIME type of {@link #CONTENT_URI} providing a directory of * presence details. */ @@ -914,28 +938,6 @@ public final class ContactsContract { } /** - * Custom ringtone associated with the contact. - */ - public static final class CustomRingtone implements BaseCommonColumns { - private CustomRingtone() {} - - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/custom_ringtone"; - - /** - * Whether to send the number to voicemail. - * <P>Type: INTEGER (if set, non-0 means true)</P> - */ - public static final String SEND_TO_VOICEMAIL = "data1"; - - /** - * The ringtone uri. - * <P>Type: TEXT</P> - */ - public static final String RINGTONE_URI = "data2"; - } - - /** * Group Membership. */ public static final class GroupMembership implements BaseCommonColumns { diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 51d1951..bc7b5be 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -344,7 +344,10 @@ public final class MediaStore // Check if file exists with a FileInputStream FileInputStream stream = new FileInputStream(imagePath); try { - return insertImage(cr, BitmapFactory.decodeFile(imagePath), name, description); + Bitmap bm = BitmapFactory.decodeFile(imagePath); + String ret = insertImage(cr, bm, name, description); + bm.recycle(); + return ret; } finally { try { stream.close(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ebe54fc..303d3fc 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1290,6 +1290,50 @@ public final class Settings { public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone"; /** + * CDMA only settings + * DTMF tone type played by the dialer when dialing. + * 0 = Normal + * 1 = Long + * @hide + */ + public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type"; + + /** + * CDMA only settings + * Emergency Tone 0 = Off + * 1 = Alert + * 2 = Vibrate + * @hide + */ + public static final String EMERGENCY_TONE = "emergency_tone"; + + /** + * CDMA only settings + * Whether the auto retry is enabled. The value is + * boolean (1 or 0). + * @hide + */ + public static final String CALL_AUTO_RETRY = "call_auto_retry"; + + /** + * Whether the hearing aid is enabled. The value is + * boolean (1 or 0). + * @hide + */ + public static final String HEARING_AID = "hearing_aid"; + + /** + * CDMA only settings + * TTY Mode + * 0 = OFF + * 1 = FULL + * 2 = VCO + * 3 = HCO + * @hide + */ + public static final String TTY_MODE = "tty_mode"; + + /** * Whether the sounds effects (key clicks, lid open ...) are enabled. The value is * boolean (1 or 0). */ @@ -1884,6 +1928,12 @@ public final class Settings { public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; /** + * Whether assisted GPS should be enabled or not. + * @hide + */ + public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled"; + + /** * The Logging ID (a unique 64-bit value) as a hex string. * Used as a pseudonymous identifier for logging. * @deprecated This identifier is poorly initialized and has @@ -2712,6 +2762,12 @@ public final class Settings { "gtalk_max_retries_for_auth_expired"; /** + * This is the url for getting the app token for server-to-device data messaging. + */ + public static final String DATA_MESSAGE_GET_APP_TOKEN_URL = + "data_messaging_get_app_token_url"; + + /** * Enable use of ssl session caching. * 'db' - save each session in a (per process) database * 'file' - save each session in a (per process) file diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 75c590d..77b1b1d 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -545,15 +545,28 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { Log.e(TAG, "*Error*: GetAdapterProperties returned NULL"); return; } - for (int i = 0; i < properties.length; i+=2) { - String value = null; - if (mProperties.containsKey(properties[i])) { - value = mProperties.get(properties[i]); - value = value + ',' + properties[i+1]; - } else - value = properties[i+1]; - mProperties.put(properties[i], value); + for (int i = 0; i < properties.length; i++) { + String name = properties[i]; + String newValue; + int len; + if (name == null) { + Log.e(TAG, "Error:Adapter Property at index" + i + "is null"); + continue; + } + if (name.equals("Devices")) { + len = Integer.valueOf(properties[++i]); + if (len != 0) + newValue = ""; + else + newValue = null; + for (int j = 0; j < len; j++) { + newValue += properties[++i] + ","; + } + } else { + newValue = properties[++i]; + } + mProperties.put(name, newValue); } // Add adapter object path property. @@ -819,15 +832,27 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { propertyValues = new HashMap<String, String>(); } - for (int i = 0; i < properties.length; i+=2) { - String value = null; - if (propertyValues.containsKey(properties[i])) { - value = propertyValues.get(properties[i]); - value = value + ',' + properties[i+1]; + for (int i = 0; i < properties.length; i++) { + String name = properties[i]; + String newValue; + int len; + if (name == null) { + Log.e(TAG, "Error: Remote Device Property at index" + i + "is null"); + continue; + } + if (name.equals("UUIDs") || name.equals("Nodes")) { + len = Integer.valueOf(properties[++i]); + if (len != 0) + newValue = ""; + else + newValue = null; + for (int j = 0; j < len; j++) { + newValue += properties[++i] + ","; + } } else { - value = properties[i+1]; + newValue = properties[++i]; } - propertyValues.put(properties[i], value); + propertyValues.put(name, newValue); } mRemoteDeviceProperties.put(address, propertyValues); } diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index ed66dce..38eb4d7 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -260,11 +260,15 @@ class BluetoothEventLoop { mContext.sendBroadcast(intent, BLUETOOTH_PERM); mBluetoothService.setProperty(name, propValues[1]); } else if (name.equals("Devices")) { - String value = ""; - for (int i = 1; i < propValues.length; i++) { - value = value + propValues[i] + ','; + String value = null; + int len = Integer.valueOf(propValues[1]); + if (len > 0) { + value = ""; + for (int i = 2; i < propValues.length; i++) { + value = value + propValues[i] + ','; + } } - mBluetoothService.setProperty(name, value.equals("") ? null : value); + mBluetoothService.setProperty(name, value); } else if (name.equals("Powered")) { // bluetoothd has restarted, re-read all our properties. // Note: bluez only sends this property change when it restarts. @@ -303,12 +307,15 @@ class BluetoothEventLoop { mContext.sendBroadcast(intent, BLUETOOTH_PERM); mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); } else if (name.equals("UUIDs")) { - String uuid = "" ; - for (int i = 1; i < propValues.length; i++) { - uuid = uuid + propValues[i] + ","; + String uuid = null; + int len = Integer.valueOf(propValues[1]); + if (len > 0) { + uuid = ""; + for (int i = 2; i < propValues.length; i++) { + uuid = uuid + propValues[i] + ","; + } } - mBluetoothService.setRemoteDeviceProperty(address, name, - uuid.equals("") ? null : uuid); + mBluetoothService.setRemoteDeviceProperty(address, name, uuid); } } diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl index 75c3b30..15f3876 100755 --- a/core/java/android/speech/tts/ITts.aidl +++ b/core/java/android/speech/tts/ITts.aidl @@ -33,6 +33,8 @@ interface ITts { void speak(in String text, in int queueMode, in String[] params);
+ void speakIpa(in String ipaText, in int queueMode, in String[] params);
+
boolean isSpeaking();
void stop();
@@ -41,9 +43,15 @@ interface ITts { void addSpeechFile(in String text, in String filename);
+ String[] getLanguage();
+
+ int isLanguageAvailable(in String language, in String country, in String variant);
+
void setLanguage(in String language, in String country, in String variant);
- boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory);
+ boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory); +
+ boolean synthesizeIpaToFile(in String ipaText, in String[] params, in String outputDirectory);
void playEarcon(in String earcon, in int queueMode, in String[] params);
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index c064284..b245713 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -46,10 +46,6 @@ public class TextToSpeech { * Denotes a generic operation failure. */ public static final int TTS_ERROR = -1; - /** - * Denotes a failure due to a missing resource. - */ - public static final int TTS_ERROR_MISSING_RESOURCE = -2; /** * Queue mode where all entries in the playback queue (media to be played @@ -61,6 +57,37 @@ public class TextToSpeech { */ public static final int TTS_QUEUE_ADD = 1; + + /** + * Denotes the language is available exactly as specified by the locale + */ + public static final int TTS_LANG_COUNTRY_VAR_AVAILABLE = 2; + + + /** + * Denotes the language is available for the language and country specified + * by the locale, but not the variant. + */ + public static final int TTS_LANG_COUNTRY_AVAILABLE = 1; + + + /** + * Denotes the language is available for the language by the locale, + * but not the country and variant. + */ + public static final int TTS_LANG_AVAILABLE = 0; + + /** + * Denotes the language data is missing. + */ + public static final int TTS_LANG_MISSING_DATA = -1; + + /** + * Denotes the language is not supported by the current TTS engine. + */ + public static final int TTS_LANG_NOT_SUPPORTED = -2; + + /** * Called when the TTS has initialized. * @@ -72,15 +99,6 @@ public class TextToSpeech { } /** - * Called when the TTS has finished speaking by itself (speaking - * finished without being canceled). - * - */ - public interface OnSpeechCompletedListener { - public void onSpeechCompleted(); - } - - /** * Internal constants for the TTS functionality * * {@hide} @@ -100,6 +118,16 @@ public class TextToSpeech { public static final int CHECK_VOICE_DATA_BAD_DATA = -1; public static final int CHECK_VOICE_DATA_MISSING_DATA = -2; public static final int CHECK_VOICE_DATA_MISSING_DATA_NO_SDCARD = -3; + + // keys for the parameters passed with speak commands + public static final String TTS_KEY_PARAM_RATE = "rate"; + public static final String TTS_KEY_PARAM_LANGUAGE = "language"; + public static final String TTS_KEY_PARAM_COUNTRY = "country"; + public static final String TTS_KEY_PARAM_VARIANT = "variant"; + public static final int TTS_PARAM_POSITION_RATE = 0; + public static final int TTS_PARAM_POSITION_LANGUAGE = 2; + public static final int TTS_PARAM_POSITION_COUNTRY = 4; + public static final int TTS_PARAM_POSITION_VARIANT = 6; } /** @@ -112,11 +140,11 @@ public class TextToSpeech { private OnInitListener mInitListener = null; private boolean mStarted = false; private final Object mStartLock = new Object(); - private ITtsCallback mITtsCallback; - private OnSpeechCompletedListener mSpeechCompListener = null; - private final Object mSpeechCompListenerLock = new Object(); - - + private int mCachedRate = Engine.FALLBACK_TTS_DEFAULT_RATE; + private String mCachedLang = Engine.FALLBACK_TTS_DEFAULT_LANG; + private String mCachedCountry = Engine.FALLBACK_TTS_DEFAULT_COUNTRY; + private String mCachedVariant = Engine.FALLBACK_TTS_DEFAULT_VARIANT; + private String[] mCachedParams; /** * The constructor for the TTS. @@ -130,24 +158,23 @@ public class TextToSpeech { public TextToSpeech(Context context, OnInitListener listener) { mContext = context; mInitListener = listener; - initTts(); - } + mCachedParams = new String[2*4]; //4 parameters, store key and value + mCachedParams[Engine.TTS_PARAM_POSITION_RATE] = Engine.TTS_KEY_PARAM_RATE; + mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE] = Engine.TTS_KEY_PARAM_LANGUAGE; + mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY] = Engine.TTS_KEY_PARAM_COUNTRY; + mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT] = Engine.TTS_KEY_PARAM_VARIANT; + updateCachedParamArray(); - public void setOnSpeechCompletedListener(final OnSpeechCompletedListener listener) { - synchronized(mSpeechCompListenerLock) { - mSpeechCompListener = listener; - } + initTts(); } - private boolean dataFilesCheck() { - // TODO #TTS# config manager will be in settings - Log.i("TTS_FIXME", "FIXME in Tts: config manager will be in settings"); - // TODO #TTS# implement checking of the correct installation of - // the data files. - - return true; + private void updateCachedParamArray() { + mCachedParams[Engine.TTS_PARAM_POSITION_RATE+1] = String.valueOf(mCachedRate); + mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE+1] = mCachedLang; + mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY+1] = mCachedCountry; + mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT+1] = mCachedVariant; } @@ -159,34 +186,7 @@ public class TextToSpeech { public void onServiceConnected(ComponentName name, IBinder service) { synchronized(mStartLock) { mITts = ITts.Stub.asInterface(service); - try { - mITtsCallback = new ITtsCallback.Stub() { - public void markReached(String mark) - throws RemoteException { - // call the listener of that event, but not - // while locked. - OnSpeechCompletedListener listener = null; - synchronized(mSpeechCompListenerLock) { - listener = mSpeechCompListener; - } - if (listener != null) { - listener.onSpeechCompleted(); - } - } - }; - mITts.registerCallback(mITtsCallback); - - } catch (RemoteException e) { - initTts(); - return; - } - mStarted = true; - // The callback can become null if the Android OS decides to - // restart the TTS process as well as whatever is using it. - // In such cases, do nothing - the error handling from the - // speaking calls will kick in and force a proper restart of - // the TTS. if (mInitListener != null) { // TODO manage failures and missing resources mInitListener.onInit(TTS_SUCCESS); @@ -251,14 +251,17 @@ public class TextToSpeech { * * @param resourceId * Example: <b><code>R.raw.south_south_east</code></b> + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public void addSpeech(String text, String packagename, int resourceId) { + public int addSpeech(String text, String packagename, int resourceId) { synchronized(mStartLock) { if (!mStarted) { - return; + return TTS_ERROR; } try { mITts.addSpeech(text, packagename, resourceId); + return TTS_SUCCESS; } catch (RemoteException e) { // TTS died; restart it. mStarted = false; @@ -272,6 +275,7 @@ public class TextToSpeech { mStarted = false; initTts(); } + return TTS_ERROR; } } @@ -285,14 +289,17 @@ public class TextToSpeech { * @param filename * The full path to the sound file (for example: * "/sdcard/mysounds/hello.wav") + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public void addSpeech(String text, String filename) { + public int addSpeech(String text, String filename) { synchronized (mStartLock) { if (!mStarted) { - return; + return TTS_ERROR; } try { mITts.addSpeechFile(text, filename); + return TTS_SUCCESS; } catch (RemoteException e) { // TTS died; restart it. mStarted = false; @@ -306,6 +313,7 @@ public class TextToSpeech { mStarted = false; initTts(); } + return TTS_ERROR; } } @@ -324,17 +332,20 @@ public class TextToSpeech { * See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH. * @param params * The hashmap of speech parameters to be used. + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public void speak(String text, int queueMode, HashMap<String,String> params) + public int speak(String text, int queueMode, HashMap<String,String> params) { synchronized (mStartLock) { Log.i("TTS received: ", text); if (!mStarted) { - return; + return TTS_ERROR; } try { - // TODO support extra parameters, passing null for the moment - mITts.speak(text, queueMode, null); + // TODO support extra parameters, passing cache of current parameters for the moment + mITts.speak(text, queueMode, mCachedParams); + return TTS_SUCCESS; } catch (RemoteException e) { // TTS died; restart it. mStarted = false; @@ -348,6 +359,55 @@ public class TextToSpeech { mStarted = false; initTts(); } + return TTS_ERROR; + } + } + + + /** + * Speaks the IPA string using the specified queuing strategy and speech + * parameters. Note that the speech parameters are not universally supported + * by all engines and will be treated as a hint. The TTS library will try to + * fulfill these parameters as much as possible, but there is no guarantee + * that the voice used will have the properties specified. + * + * @param ipaText + * The string of IPA text to be spoken. + * @param queueMode + * The queuing strategy to use. + * See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH. + * @param params + * The hashmap of speech parameters to be used. + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. + * + * {@hide} + */ + public int speakIpa(String ipaText, int queueMode, HashMap<String,String> params) + { + synchronized (mStartLock) { + Log.i("TTS received: ", ipaText); + if (!mStarted) { + return TTS_ERROR; + } + try { + // TODO support extra parameters, passing cache of current parameters for the moment + mITts.speakIpa(ipaText, queueMode, mCachedParams); + return TTS_SUCCESS; + } catch (RemoteException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } + return TTS_ERROR; } } @@ -361,16 +421,19 @@ public class TextToSpeech { * See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH. * @param params * The hashmap of parameters to be used. + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public void playEarcon(String earcon, int queueMode, + public int playEarcon(String earcon, int queueMode, HashMap<String,String> params) { synchronized (mStartLock) { if (!mStarted) { - return; + return TTS_ERROR; } try { // TODO support extra parameters, passing null for the moment mITts.playEarcon(earcon, queueMode, null); + return TTS_SUCCESS; } catch (RemoteException e) { // TTS died; restart it. mStarted = false; @@ -384,12 +447,45 @@ public class TextToSpeech { mStarted = false; initTts(); } + return TTS_ERROR; } } - - public void playSilence(long durationInMs, int queueMode) { - // TODO implement, already present in TTS service + /** + * Plays silence for the specified amount of time using the specified + * queue mode. + * + * @param durationInMs + * A long that indicates how long the silence should last. + * @param queueMode + * See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH. + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. + */ + public int playSilence(long durationInMs, int queueMode) { + synchronized (mStartLock) { + if (!mStarted) { + return TTS_ERROR; + } + try { + // TODO support extra parameters, passing cache of current parameters for the moment + mITts.playSilence(durationInMs, queueMode, mCachedParams); + return TTS_SUCCESS; + } catch (RemoteException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } + return TTS_ERROR; + } } @@ -425,14 +521,17 @@ public class TextToSpeech { /** * Stops speech from the TTS. + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public void stop() { + public int stop() { synchronized (mStartLock) { if (!mStarted) { - return; + return TTS_ERROR; } try { mITts.stop(); + return TTS_SUCCESS; } catch (RemoteException e) { // TTS died; restart it. mStarted = false; @@ -446,6 +545,7 @@ public class TextToSpeech { mStarted = false; initTts(); } + return TTS_ERROR; } } @@ -462,21 +562,27 @@ public class TextToSpeech { * The speech rate for the TTS engine. 1 is the normal speed, * lower values slow down the speech (0.5 is half the normal speech rate), * greater values accelerate it (2 is twice the normal speech rate). + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public void setSpeechRate(float speechRate) { + public int setSpeechRate(float speechRate) { synchronized (mStartLock) { if (!mStarted) { - return; + return TTS_SUCCESS; } try { if (speechRate > 0) { - mITts.setSpeechRate((int)(speechRate*100)); + mCachedRate = (int)(speechRate*100); + updateCachedParamArray(); + mITts.setSpeechRate(mCachedRate); + return TTS_SUCCESS; } } catch (RemoteException e) { // TTS died; restart it. mStarted = false; initTts(); } + return TTS_ERROR; } } @@ -493,21 +599,25 @@ public class TextToSpeech { * The pitch for the TTS engine. 1 is the normal pitch, * lower values lower the tone of the synthesized voice, * greater values increase it. + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public void setPitch(float pitch) { + public int setPitch(float pitch) { synchronized (mStartLock) { if (!mStarted) { - return; + return TTS_ERROR; } try { if (pitch > 0) { mITts.setPitch((int)(pitch*100)); + return TTS_SUCCESS; } } catch (RemoteException e) { // TTS died; restart it. mStarted = false; initTts(); } + return TTS_ERROR; } } @@ -521,25 +631,86 @@ public class TextToSpeech { * * @param loc * The locale describing the language to be used. + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public void setLanguage(Locale loc) { + public int setLanguage(Locale loc) { synchronized (mStartLock) { if (!mStarted) { - return; + return TTS_ERROR; } try { - mITts.setLanguage(loc.getISO3Language(), loc.getISO3Country(), loc.getVariant()); + mCachedLang = loc.getISO3Language(); + mCachedCountry = loc.getISO3Country(); + mCachedVariant = loc.getVariant(); + updateCachedParamArray(); + mITts.setLanguage(mCachedLang, mCachedCountry, mCachedVariant); + return TTS_SUCCESS; } catch (RemoteException e) { // TTS died; restart it. mStarted = false; initTts(); } + return TTS_ERROR; } } /** - * Speaks the given text using the specified queueing mode and parameters. + * Returns a Locale instance describing the language currently being used by the TTS engine. + * @return language, country (if any) and variant (if any) used by the engine stored in a Locale + * instance, or null is the TTS engine has failed. + */ + public Locale getLanguage() { + synchronized (mStartLock) { + if (!mStarted) { + return null; + } + try { + String[] locStrings = mITts.getLanguage(); + if (locStrings.length == 3) { + return new Locale(locStrings[0], locStrings[1], locStrings[2]); + } else { + return null; + } + } catch (RemoteException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } + return null; + } + } + + /** + * Checks if the specified language as represented by the Locale is available. + * + * @param loc + * The Locale describing the language to be used. + * + * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE, + * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE. + */ + public int isLanguageAvailable(Locale loc) { + synchronized (mStartLock) { + if (!mStarted) { + return TTS_LANG_NOT_SUPPORTED; + } + try { + return mITts.isLanguageAvailable(loc.getISO3Language(), loc.getISO3Country(), + loc.getVariant()); + } catch (RemoteException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } + return TTS_LANG_NOT_SUPPORTED; + } + } + + + /** + * Synthesizes the given text to a file using the specified parameters. * * @param text * The String of text that should be synthesized @@ -548,17 +719,20 @@ public class TextToSpeech { * @param filename * The string that gives the full output filename; it should be * something like "/sdcard/myappsounds/mysound.wav". - * @return A boolean that indicates if the synthesis succeeded + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public boolean synthesizeToFile(String text, HashMap<String,String> params, + public int synthesizeToFile(String text, HashMap<String,String> params, String filename) { synchronized (mStartLock) { if (!mStarted) { - return false; + return TTS_ERROR; } try { // TODO support extra parameters, passing null for the moment - return mITts.synthesizeToFile(text, null, filename); + if (mITts.synthesizeToFile(text, null, filename)){ + return TTS_SUCCESS; + } } catch (RemoteException e) { // TTS died; restart it. mStarted = false; @@ -572,9 +746,52 @@ public class TextToSpeech { mStarted = false; initTts(); } - return false; + return TTS_ERROR; } } + /** + * Synthesizes the given IPA text to a file using the specified parameters. + * + * @param text + * The String of text that should be synthesized + * @param params + * A hashmap of parameters. + * @param filename + * The string that gives the full output filename; it should be + * something like "/sdcard/myappsounds/mysound.wav". + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. + * + * {@hide} + */ + public int synthesizeIpaToFile(String ipaText, + HashMap<String,String> params, String filename) { + synchronized (mStartLock) { + if (!mStarted) { + return TTS_ERROR; + } + try { + // TODO support extra parameters, passing null for the moment + if (mITts.synthesizeIpaToFile(ipaText, null, filename)){ + return TTS_SUCCESS; + } + } catch (RemoteException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + mStarted = false; + initTts(); + } + return TTS_ERROR; + } + } + } diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 1a4eb69..9dd8ceb 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -25,7 +25,9 @@ import android.pim.DateException; import java.util.Calendar; import java.util.Date; +import java.util.Formatter; import java.util.GregorianCalendar; +import java.util.Locale; import java.util.TimeZone; /** @@ -1040,6 +1042,31 @@ public class DateUtils /** * Formats a date or a time range according to the local conventions. + * <p> + * Note that this is a convenience method. Using it involves creating an + * internal {@link java.util.Formatter} instance on-the-fly, which is + * somewhat costly in terms of memory and time. This is probably acceptable + * if you use the method only rarely, but if you rely on it for formatting a + * large number of dates, consider creating and reusing your own + * {@link java.util.Formatter} instance and use the version of + * {@link #formatDateRange(Context, long, long, int) formatDateRange} + * that takes a {@link java.util.Formatter}. + * + * @param context the context is required only if the time is shown + * @param startMillis the start time in UTC milliseconds + * @param endMillis the end time in UTC milliseconds + * @param flags a bit mask of options See + * {@link #formatDateRange(Context, long, long, int) formatDateRange} + * @return a string containing the formatted date/time range. + */ + public static String formatDateRange(Context context, long startMillis, + long endMillis, int flags) { + Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault()); + return formatDateRange(context, f, startMillis, endMillis, flags).toString(); + } + + /** + * Formats a date or a time range according to the local conventions. * * <p> * Example output strings (date formats in these examples are shown using @@ -1181,14 +1208,17 @@ public class DateUtils * instead of "December 31, 2008". * * @param context the context is required only if the time is shown + * @param formatter the Formatter used for formatting the date range. + * Note: be sure to call setLength(0) on StringBuilder passed to + * the Formatter constructor unless you want the results to accumulate. * @param startMillis the start time in UTC milliseconds * @param endMillis the end time in UTC milliseconds * @param flags a bit mask of options * - * @return a string containing the formatted date/time range. + * @return the formatter with the formatted date/time range appended to the string buffer. */ - public static String formatDateRange(Context context, long startMillis, - long endMillis, int flags) { + public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, + long endMillis, int flags) { Resources res = Resources.getSystem(); boolean showTime = (flags & FORMAT_SHOW_TIME) != 0; boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0; @@ -1423,8 +1453,7 @@ public class DateUtils if (noMonthDay && startMonthNum == endMonthNum) { // Example: "January, 2008" - String startDateString = startDate.format(defaultDateFormat); - return startDateString; + return formatter.format("%s", startDate.format(defaultDateFormat)); } if (startYear != endYear || noMonthDay) { @@ -1436,10 +1465,9 @@ public class DateUtils // The values that are used in a fullFormat string are specified // by position. - dateRange = String.format(fullFormat, + return formatter.format(fullFormat, startWeekDayString, startDateString, startTimeString, endWeekDayString, endDateString, endTimeString); - return dateRange; } // Get the month, day, and year strings for the start and end dates @@ -1476,12 +1504,11 @@ public class DateUtils // The values that are used in a fullFormat string are specified // by position. - dateRange = String.format(fullFormat, + return formatter.format(fullFormat, startWeekDayString, startMonthString, startMonthDayString, startYearString, startTimeString, endWeekDayString, endMonthString, endMonthDayString, endYearString, endTimeString); - return dateRange; } if (startDay != endDay) { @@ -1496,12 +1523,11 @@ public class DateUtils // The values that are used in a fullFormat string are specified // by position. - dateRange = String.format(fullFormat, + return formatter.format(fullFormat, startWeekDayString, startMonthString, startMonthDayString, startYearString, startTimeString, endWeekDayString, endMonthString, endMonthDayString, endYearString, endTimeString); - return dateRange; } // Same start and end day @@ -1522,6 +1548,7 @@ public class DateUtils } else { // Example: "10:00 - 11:00 am" String timeFormat = res.getString(com.android.internal.R.string.time1_time2); + // Don't use the user supplied Formatter because the result will pollute the buffer. timeString = String.format(timeFormat, startTimeString, endTimeString); } } @@ -1545,7 +1572,7 @@ public class DateUtils fullFormat = res.getString(com.android.internal.R.string.time_date); } else { // Example: "Oct 9" - return dateString; + return formatter.format("%s", dateString); } } } else if (showWeekDay) { @@ -1554,16 +1581,15 @@ public class DateUtils fullFormat = res.getString(com.android.internal.R.string.time_wday); } else { // Example: "Tue" - return startWeekDayString; + return formatter.format("%s", startWeekDayString); } } else if (showTime) { - return timeString; + return formatter.format("%s", timeString); } // The values that are used in a fullFormat string are specified // by position. - dateRange = String.format(fullFormat, timeString, startWeekDayString, dateString); - return dateRange; + return formatter.format(fullFormat, timeString, startWeekDayString, dateString); } /** diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index d89ada0..4179edb 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -103,40 +103,43 @@ public class DisplayMetrics { /** * Update the display metrics based on the compatibility info and orientation + * NOTE: DO NOT EXPOSE THIS API! It is introducing a circular dependency + * with the higher-level android.res package. * {@hide} */ - public void updateMetrics(CompatibilityInfo compatibilityInfo, int orientation) { + public void updateMetrics(CompatibilityInfo compatibilityInfo, int orientation, + int screenLayout) { int xOffset = 0; if (!compatibilityInfo.isConfiguredExpandable()) { // Note: this assume that configuration is updated before calling // updateMetrics method. - int defaultWidth; - int defaultHeight; - switch (orientation) { - case Configuration.ORIENTATION_LANDSCAPE: { - defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density); - defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density); - break; - } - case Configuration.ORIENTATION_PORTRAIT: - case Configuration.ORIENTATION_SQUARE: - default: { - defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density); - defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density); - break; - } - case Configuration.ORIENTATION_UNDEFINED: { - // don't change - return; - } - } - - if (defaultWidth == widthPixels && defaultHeight == heightPixels) { - // the screen size is same as expected size. make it expandable - compatibilityInfo.setExpandable(true); - } else { + if (screenLayout == Configuration.SCREENLAYOUT_LARGE) { + // This is a large screen device and the app is not + // compatible with large screens, to diddle it. + compatibilityInfo.setExpandable(false); - // adjust the size only when the device's screen is bigger. + // Figure out the compatibility width and height of the screen. + int defaultWidth; + int defaultHeight; + switch (orientation) { + case Configuration.ORIENTATION_LANDSCAPE: { + defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density); + defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density); + break; + } + case Configuration.ORIENTATION_PORTRAIT: + case Configuration.ORIENTATION_SQUARE: + default: { + defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density); + defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density); + break; + } + case Configuration.ORIENTATION_UNDEFINED: { + // don't change + return; + } + } + if (defaultWidth < widthPixels) { // content/window's x offset in original pixels xOffset = ((widthPixels - defaultWidth) / 2); @@ -145,6 +148,10 @@ public class DisplayMetrics { if (defaultHeight < heightPixels) { heightPixels = defaultHeight; } + + } else { + // the screen size is same as expected size. make it expandable + compatibilityInfo.setExpandable(true); } } compatibilityInfo.setVisibleRect(xOffset, widthPixels, heightPixels); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3bfdde8..b3180ca 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1690,6 +1690,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility private int[] mDrawableState = null; private SoftReference<Bitmap> mDrawingCache; + private SoftReference<Bitmap> mUnscaledDrawingCache; /** * When this view has focus and the next focus is {@link #FOCUS_LEFT}, @@ -5783,28 +5784,52 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p> + * + * @return A non-scaled bitmap representing this view or null if cache is disabled. + * + * @see #getDrawingCache(boolean) + */ + public Bitmap getDrawingCache() { + return getDrawingCache(false); + } + + /** * <p>Returns the bitmap in which this view drawing is cached. The returned bitmap * is null when caching is disabled. If caching is enabled and the cache is not ready, * this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not * draw from the cache when the cache is enabled. To benefit from the cache, you must * request the drawing cache by calling this method and draw it on screen if the * returned bitmap is not null.</p> - * - * @return a bitmap representing this view or null if cache is disabled - * + * + * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled, + * this method will create a bitmap of the same size as this view. Because this bitmap + * will be drawn scaled by the parent ViewGroup, the result on screen might show + * scaling artifacts. To avoid such artifacts, you should call this method by setting + * the auto scaling to true. Doing so, however, will generate a bitmap of a different + * size than the view. This implies that your application must be able to handle this + * size.</p> + * + * @param autoScale Indicates whether the generated bitmap should be scaled based on + * the current density of the screen when the application is in compatibility + * mode. + * + * @return A bitmap representing this view or null if cache is disabled. + * * @see #setDrawingCacheEnabled(boolean) * @see #isDrawingCacheEnabled() - * @see #buildDrawingCache() + * @see #buildDrawingCache(boolean) * @see #destroyDrawingCache() */ - public Bitmap getDrawingCache() { + public Bitmap getDrawingCache(boolean autoScale) { if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) { return null; } if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) { - buildDrawingCache(); + buildDrawingCache(autoScale); } - return mDrawingCache == null ? null : mDrawingCache.get(); + return autoScale ? (mDrawingCache == null ? null : mDrawingCache.get()) : + (mUnscaledDrawingCache == null ? null : mUnscaledDrawingCache.get()); } /** @@ -5823,6 +5848,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility if (bitmap != null) bitmap.recycle(); mDrawingCache = null; } + if (mUnscaledDrawingCache != null) { + final Bitmap bitmap = mUnscaledDrawingCache.get(); + if (bitmap != null) bitmap.recycle(); + mUnscaledDrawingCache = null; + } } /** @@ -5850,18 +5880,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p> + * + * @see #buildDrawingCache(boolean) + */ + public void buildDrawingCache() { + buildDrawingCache(false); + } + + /** * <p>Forces the drawing cache to be built if the drawing cache is invalid.</p> * * <p>If you call {@link #buildDrawingCache()} manually without calling * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you * should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p> + * + * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled, + * this method will create a bitmap of the same size as this view. Because this bitmap + * will be drawn scaled by the parent ViewGroup, the result on screen might show + * scaling artifacts. To avoid such artifacts, you should call this method by setting + * the auto scaling to true. Doing so, however, will generate a bitmap of a different + * size than the view. This implies that your application must be able to handle this + * size.</p> * * @see #getDrawingCache() * @see #destroyDrawingCache() */ - public void buildDrawingCache() { - if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null || - mDrawingCache.get() == null) { + public void buildDrawingCache(boolean autoScale) { + if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ? + (mDrawingCache == null || mDrawingCache.get() == null) : + (mUnscaledDrawingCache == null || mUnscaledDrawingCache.get() == null))) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE); @@ -5874,12 +5922,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility int height = mBottom - mTop; final AttachInfo attachInfo = mAttachInfo; - if (attachInfo != null) { - final boolean scalingRequired = attachInfo.mScalingRequired; - if (scalingRequired) { - width = (int) ((width * attachInfo.mApplicationScale) + 0.5f); - height = (int) ((height * attachInfo.mApplicationScale) + 0.5f); - } + final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired; + + if (autoScale && scalingRequired) { + width = (int) ((width * attachInfo.mApplicationScale) + 0.5f); + height = (int) ((height * attachInfo.mApplicationScale) + 0.5f); } final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor; @@ -5894,7 +5941,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } boolean clear = true; - Bitmap bitmap = mDrawingCache == null ? null : mDrawingCache.get(); + Bitmap bitmap = autoScale ? (mDrawingCache == null ? null : mDrawingCache.get()) : + (mUnscaledDrawingCache == null ? null : mUnscaledDrawingCache.get()); if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) { @@ -5923,12 +5971,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility try { bitmap = Bitmap.createBitmap(width, height, quality); - mDrawingCache = new SoftReference<Bitmap>(bitmap); + if (autoScale) { + mDrawingCache = new SoftReference<Bitmap>(bitmap); + } else { + mUnscaledDrawingCache = new SoftReference<Bitmap>(bitmap); + } } catch (OutOfMemoryError e) { // If there is not enough memory to create the bitmap cache, just // ignore the issue as bitmap caches are not required to draw the // view hierarchy - mDrawingCache = null; + if (autoScale) { + mDrawingCache = null; + } else { + mUnscaledDrawingCache = null; + } return; } @@ -5940,13 +5996,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility canvas = attachInfo.mCanvas; if (canvas == null) { canvas = new Canvas(); - - // NOTE: This should have to happen only once since compatibility - // mode should not change at runtime - if (attachInfo.mScalingRequired) { - final float scale = attachInfo.mApplicationScale; - canvas.scale(scale, scale); - } } canvas.setBitmap(bitmap); // Temporarily clobber the cached Canvas in case one of our children @@ -5965,6 +6014,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility computeScroll(); final int restoreCount = canvas.save(); + + if (autoScale && scalingRequired) { + final float scale = attachInfo.mApplicationScale; + canvas.scale(scale, scale); + } + canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= DRAWN; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index f803b5a..f7b7f02 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1166,7 +1166,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { child.setDrawingCacheEnabled(true); - child.buildDrawingCache(); + child.buildDrawingCache(true); } } @@ -1208,7 +1208,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager bindLayoutAnimation(child); if (cache) { child.setDrawingCacheEnabled(true); - child.buildDrawingCache(); + child.buildDrawingCache(true); } } } @@ -1448,7 +1448,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager Bitmap cache = null; if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE || (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) { - cache = child.getDrawingCache(); + cache = child.getDrawingCache(true); if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired; } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 9a0f467..7393737 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -340,10 +340,19 @@ public class BaseInputConnection implements InputConnection { } /** - * The default implementation does nothing. + * The default implementation turns this into the enter key. */ public boolean performEditorAction(int actionCode) { - return false; + long eventTime = SystemClock.uptimeMillis(); + sendKeyEvent(new KeyEvent(eventTime, eventTime, + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, + KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE + | KeyEvent.FLAG_EDITOR_ACTION)); + sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, + KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, + KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE + | KeyEvent.FLAG_EDITOR_ACTION)); + return true; } /** diff --git a/core/java/android/webkit/CacheLoader.java b/core/java/android/webkit/CacheLoader.java index 3e1b602..de8f888 100644 --- a/core/java/android/webkit/CacheLoader.java +++ b/core/java/android/webkit/CacheLoader.java @@ -17,6 +17,7 @@ package android.webkit; import android.net.http.Headers; +import android.text.TextUtils; /** * This class is a concrete implementation of StreamLoader that uses a @@ -49,17 +50,22 @@ class CacheLoader extends StreamLoader { @Override protected void buildHeaders(Headers headers) { StringBuilder sb = new StringBuilder(mCacheResult.mimeType); - if (mCacheResult.encoding != null && - mCacheResult.encoding.length() > 0) { + if (!TextUtils.isEmpty(mCacheResult.encoding)) { sb.append(';'); sb.append(mCacheResult.encoding); } headers.setContentType(sb.toString()); - if (mCacheResult.location != null && - mCacheResult.location.length() > 0) { + if (!TextUtils.isEmpty(mCacheResult.location)) { headers.setLocation(mCacheResult.location); } - } + if (!TextUtils.isEmpty(mCacheResult.expiresString)) { + headers.setExpires(mCacheResult.expiresString); + } + + if (!TextUtils.isEmpty(mCacheResult.contentdisposition)) { + headers.setContentDisposition(mCacheResult.contentdisposition); + } + } } diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index 4d471f7..9a02fbe 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -79,12 +79,14 @@ public final class CacheManager { int httpStatusCode; long contentLength; long expires; + String expiresString; String localPath; String lastModified; String etag; String mimeType; String location; String encoding; + String contentdisposition; // these fields are NOT saved to the database InputStream inStream; @@ -107,6 +109,13 @@ public final class CacheManager { return expires; } + /** + * @hide Pending API council approval + */ + public String getExpiresString() { + return expiresString; + } + public String getLastModified() { return lastModified; } @@ -127,6 +136,13 @@ public final class CacheManager { return encoding; } + /** + * @hide Pending API council approval + */ + public String getContentDisposition() { + return contentdisposition; + } + // For out-of-package access to the underlying streams. public InputStream getInputStream() { return inStream; @@ -603,21 +619,27 @@ public final class CacheManager { if (location != null) ret.location = location; ret.expires = -1; - String expires = headers.getExpires(); - if (expires != null) { + ret.expiresString = headers.getExpires(); + if (ret.expiresString != null) { try { - ret.expires = HttpDateTime.parse(expires); + ret.expires = HttpDateTime.parse(ret.expiresString); } catch (IllegalArgumentException ex) { // Take care of the special "-1" and "0" cases - if ("-1".equals(expires) || "0".equals(expires)) { + if ("-1".equals(ret.expiresString) + || "0".equals(ret.expiresString)) { // make it expired, but can be used for history navigation ret.expires = 0; } else { - Log.e(LOGTAG, "illegal expires: " + expires); + Log.e(LOGTAG, "illegal expires: " + ret.expiresString); } } } + String contentDisposition = headers.getContentDisposition(); + if (contentDisposition != null) { + ret.contentdisposition = contentDisposition; + } + String lastModified = headers.getLastModified(); if (lastModified != null) ret.lastModified = lastModified; diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index c407044..9a8c3c0 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -147,6 +147,16 @@ class CallbackProxy extends Handler { } /** + * Get the WebChromeClient. + * @return the current WebChromeClient instance. + * + *@hide pending API council approval. + */ + public WebChromeClient getWebChromeClient() { + return mWebChromeClient; + } + + /** * Set the client DownloadListener. * @param client An implementation of DownloadListener. */ diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java index 89cb606..8e25395 100644 --- a/core/java/android/webkit/DebugFlags.java +++ b/core/java/android/webkit/DebugFlags.java @@ -42,6 +42,7 @@ class DebugFlags { public static final boolean WEB_BACK_FORWARD_LIST = false; public static final boolean WEB_SETTINGS = false; public static final boolean WEB_SYNC_MANAGER = false; + public static final boolean WEB_TEXT_VIEW = false; public static final boolean WEB_VIEW = false; public static final boolean WEB_VIEW_CORE = false; diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java index c33744e..f98c5d3 100644 --- a/core/java/android/webkit/FrameLoader.java +++ b/core/java/android/webkit/FrameLoader.java @@ -364,7 +364,7 @@ class FrameLoader { String cookie = CookieManager.getInstance().getCookie( mListener.getWebAddress()); if (cookie != null && cookie.length() > 0) { - mHeaders.put("cookie", cookie); + mHeaders.put("Cookie", cookie); } } } diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java new file mode 100644 index 0000000..973cb01 --- /dev/null +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -0,0 +1,128 @@ +/* + * 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.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.View; +import android.widget.MediaController; +import android.widget.VideoView; + +import java.util.HashMap; + +/** + * <p>A View that displays Videos. Instances of this class + * are created on the WebCore thread. However, their code + * executes on the UI thread. Right now there is only one + * such view for fullscreen video rendering. + * + */ +class HTML5VideoViewProxy extends Handler { + // Logging tag. + private static final String LOGTAG = "HTML5VideoViewProxy"; + + // Message Ids + private static final int INIT = 100; + private static final int PLAY = 101; + + // The singleton instance. + private static HTML5VideoViewProxy sInstance; + // The VideoView driven via this proxy. + private VideoView mVideoView; + // The context object used to initialize the VideoView and the + // MediaController. + private Context mContext; + + /** + * Private constructor. + * @param context is the application context. + */ + private HTML5VideoViewProxy(Context context) { + // This handler is for the main (UI) thread. + super(Looper.getMainLooper()); + // Save the context object. + mContext = context; + // Send a message to the UI thread to create the VideoView. + // This need to be done on the UI thread, or else the + // event Handlers used by the VideoView and MediaController + // will be attached to the wrong thread. + Message message = obtainMessage(INIT); + sendMessage(message); + } + + @Override + public void handleMessage(Message msg) { + // This executes on the UI thread. + switch (msg.what) { + case INIT: + // Create the video view and set a default controller. + mVideoView = new VideoView(mContext); + mVideoView.setMediaController(new MediaController(mContext)); + break; + case PLAY: + // Check if the fullscreen video view is currently playing. + // If it is, ignore the message. + if (!mVideoView.isPlaying()) { + HashMap<String, Object> map = + (HashMap<String, Object>) msg.obj; + String url = (String) map.get("url"); + WebView webview = (WebView) map.get("webview"); + WebChromeClient client = webview.getWebChromeClient(); + if (client != null) { + mVideoView.setVideoURI(Uri.parse(url)); + mVideoView.start(); + client.onShowCustomView(mVideoView); + } + } + break; + } + } + + /** + * Play a video stream. + * @param url is the URL of the video stream. + * @param webview is the WebViewCore that is requesting the playback. + */ + public void play(String url, WebViewCore webviewCore) { + // We need to know the webview that is requesting the playback. + Message message = obtainMessage(PLAY); + HashMap<String, Object> map = new HashMap(); + map.put("url", url); + map.put("webview", webviewCore.getWebView()); + message.obj = map; + sendMessage(message); + } + + /** + * The factory for HTML5VideoViewProxy instances. Right now, + * it only produces a singleton. + * @param webViewCore is the WebViewCore that is requesting the proxy. + * + * @return the HTML5VideoViewProxy singleton. + */ + public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore) { + if (sInstance == null) { + sInstance = new HTML5VideoViewProxy(webViewCore.getWebView().getContext()); + } + return sInstance; + } +} diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index 08854f7..6f228a4 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -948,8 +948,7 @@ class LoadListener extends Handler implements EventHandler { // pass content-type content-length and content-encoding final int nativeResponse = nativeCreateResponse( mUrl, statusCode, mStatusText, - mMimeType, mContentLength, mEncoding, - mCacheResult == null ? 0 : mCacheResult.expires / 1000); + mMimeType, mContentLength, mEncoding); if (mHeaders != null) { mHeaders.getHeaders(new Headers.HeaderCallback() { public void header(String name, String value) { @@ -1460,12 +1459,11 @@ class LoadListener extends Handler implements EventHandler { * @param expectedLength An estimate of the content length or the length * given by the server. * @param encoding HTTP encoding. - * @param expireTime HTTP expires converted to seconds since the epoch. * @return The native response pointer. */ private native int nativeCreateResponse(String url, int statusCode, String statusText, String mimeType, long expectedLength, - String encoding, long expireTime); + String encoding); /** * Add a response header to the native object. diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 754b1d9..19e39ed 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -18,6 +18,7 @@ package android.webkit; import android.graphics.Bitmap; import android.os.Message; +import android.view.View; public class WebChromeClient { @@ -44,6 +45,23 @@ public class WebChromeClient { public void onReceivedIcon(WebView view, Bitmap icon) {} /** + * Notify the host application that the current page would + * like to show a custom View. + * @param view is the View object to be shown. + * + * @hide pending council approval + */ + public void onShowCustomView(View view) {} + + /** + * Notify the host application that the current page would + * like to hide its custom view. + * + * @hide pending council approval + */ + public void onHideCustomView() {} + + /** * Request the host application to create a new Webview. The host * application should handle placement of the new WebView in the view * system. The default behavior returns null. diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index f57c647..0ee3a60 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -69,7 +69,24 @@ public class WebSettings { } int value; } - + + /** + * Enum for specifying the WebView's desired density. + * FAR makes 100% looking like in 240dpi + * MEDIUM makes 100% looking like in 160dpi + * CLOSE makes 100% looking like in 120dpi + * @hide Pending API council approval + */ + public enum ZoomDensity { + FAR(150), // 240dpi + MEDIUM(100), // 160dpi + CLOSE(75); // 120dpi + ZoomDensity(int size) { + value = size; + } + int value; + } + /** * Default cache usage pattern Use with {@link #setCacheMode}. */ @@ -105,6 +122,8 @@ public class WebSettings { LOW } + // WebView associated with this WebSettings. + private WebView mWebView; // BrowserFrame used to access the native frame pointer. private BrowserFrame mBrowserFrame; // Flag to prevent multiple SYNC messages at one time. @@ -149,6 +168,7 @@ public class WebSettings { // Don't need to synchronize the get/set methods as they // are basic types, also none of these values are used in // native WebCore code. + private ZoomDensity mDefaultZoom = ZoomDensity.MEDIUM; private RenderPriority mRenderPriority = RenderPriority.NORMAL; private int mOverrideCacheMode = LOAD_DEFAULT; private boolean mSaveFormData = true; @@ -232,13 +252,13 @@ public class WebSettings { // User agent strings. private static final String DESKTOP_USERAGENT = - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en)" - + " AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2" - + " Safari/525.20.1"; + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-us)" + + " AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0" + + " Safari/530.17"; private static final String IPHONE_USERAGENT = - "Mozilla/5.0 (iPhone; U; CPU iPhone 2_1 like Mac OS X; en)" - + " AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2" - + " Mobile/5F136 Safari/525.20.1"; + "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)" + + " AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0" + + " Mobile/7A341 Safari/528.16"; private static Locale sLocale; private static Object sLockForLocaleSettings; @@ -246,9 +266,10 @@ public class WebSettings { * Package constructor to prevent clients from creating a new settings * instance. */ - WebSettings(Context context) { + WebSettings(Context context, WebView webview) { mEventHandler = new EventHandler(); mContext = context; + mWebView = webview; mDefaultTextEncoding = context.getString(com.android.internal. R.string.default_text_encoding); @@ -456,6 +477,31 @@ public class WebSettings { } /** + * Set the default zoom density of the page. This should be called from UI + * thread. + * @param zoom A ZoomDensity value + * @see WebSettings.ZoomDensity + * @hide Pending API council approval + */ + public void setDefaultZoom(ZoomDensity zoom) { + if (mDefaultZoom != zoom) { + mDefaultZoom = zoom; + mWebView.updateDefaultZoomDensity(zoom.value); + } + } + + /** + * Get the default zoom density of the page. This should be called from UI + * thread. + * @return A ZoomDensity value + * @see WebSettings.ZoomDensity + * @hide Pending API council approval + */ + public ZoomDensity getDefaultZoom() { + return mDefaultZoom; + } + + /** * Enables using light touches to make a selection and activate mouseovers. */ public void setLightTouchEnabled(boolean enabled) { diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 4a8fa3c..25a20f2 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -17,20 +17,21 @@ package android.webkit; import android.content.Context; -import android.graphics.Rect; import android.text.Editable; import android.text.InputFilter; import android.text.Selection; import android.text.Spannable; import android.text.TextUtils; import android.text.method.MovementMethod; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; +import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputConnection; import android.widget.AbsoluteLayout.LayoutParams; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; @@ -45,6 +46,8 @@ import java.util.ArrayList; */ /* package */ class WebTextView extends AutoCompleteTextView { + static final String LOGTAG = "webtextview"; + private WebView mWebView; private boolean mSingle; private int mWidthSpec; @@ -54,9 +57,6 @@ import java.util.ArrayList; // on the enter key. The method for blocking unmatched key ups prevents // the shift key from working properly. private boolean mGotEnterDown; - // mScrollToAccommodateCursor being set to false prevents us from scrolling - // the cursor on screen when using the trackball to select a textfield. - private boolean mScrollToAccommodateCursor; private int mMaxLength; // Keep track of the text before the change so we know whether we actually // need to send down the DOM events. @@ -82,6 +82,7 @@ import java.util.ArrayList; setImeOptions(EditorInfo.IME_ACTION_NONE); // Allow webkit's drawing to show through setWillNotDraw(true); + setCursorVisible(false); } @Override @@ -184,7 +185,6 @@ import java.util.ArrayList; if (maxedOut && !isArrowKey && keyCode != KeyEvent.KEYCODE_DEL) { if (oldEnd == oldStart) { // Return true so the key gets dropped. - mScrollToAccommodateCursor = true; return true; } else if (!oldText.equals(getText().toString())) { // FIXME: This makes the text work properly, but it @@ -198,7 +198,6 @@ import java.util.ArrayList; int newEnd = Selection.getSelectionEnd(span); mWebView.replaceTextfieldText(0, oldLength, span.toString(), newStart, newEnd); - mScrollToAccommodateCursor = true; return true; } } @@ -214,7 +213,6 @@ import java.util.ArrayList; sendDomEvent(event); } */ - mScrollToAccommodateCursor = true; return true; } // Ignore the key up event for newlines. This prevents @@ -265,9 +263,25 @@ import java.util.ArrayList; return ptr == mNodePointer; } + @Override public InputConnection onCreateInputConnection( + EditorInfo outAttrs) { + InputConnection connection = super.onCreateInputConnection(outAttrs); + if (mWebView != null) { + // Use the name of the textfield + the url. Use backslash as an + // arbitrary separator. + outAttrs.fieldName = mWebView.nativeFocusCandidateName() + "\\" + + mWebView.getUrl(); + } + return connection; + } + @Override protected void onSelectionChanged(int selStart, int selEnd) { if (mWebView != null) { + if (DebugFlags.WEB_TEXT_VIEW) { + Log.v(LOGTAG, "onSelectionChanged selStart=" + selStart + + " selEnd=" + selEnd); + } mWebView.setSelection(selStart, selEnd); } } @@ -313,6 +327,10 @@ import java.util.ArrayList; } else { // This corrects the selection which may have been affected by the // trackball or auto-correct. + if (DebugFlags.WEB_TEXT_VIEW) { + Log.v(LOGTAG, "onTextChanged start=" + start + + " start + before=" + (start + before)); + } mWebView.setSelection(start, start + before); } if (!cannotUseKeyEvents) { @@ -348,12 +366,6 @@ import java.util.ArrayList; // Selection is changed in onSelectionChanged return true; } - // If the user is in a textfield, and the movement method is not - // handling the trackball events, it means they are at the end of the - // field and continuing to move the trackball. In this case, we should - // not scroll the cursor on screen bc the user may be attempting to - // scroll the page, possibly in the opposite direction of the cursor. - mScrollToAccommodateCursor = false; return false; } @@ -367,11 +379,6 @@ import java.util.ArrayList; getWindowToken(), 0); mWebView.removeView(this); mWebView.requestFocus(); - mScrollToAccommodateCursor = false; - } - - /* package */ void enableScrollOnScreen(boolean enable) { - mScrollToAccommodateCursor = enable; } /* package */ void bringIntoView() { @@ -380,14 +387,6 @@ import java.util.ArrayList; } } - @Override - public boolean requestRectangleOnScreen(Rect rectangle) { - if (mScrollToAccommodateCursor) { - return super.requestRectangleOnScreen(rectangle); - } - return false; - } - /** * Send the DOM events for the specified event. * @param event KeyEvent to be translated into a DOM event. @@ -542,6 +541,10 @@ import java.util.ArrayList; } else if (start > length) { start = length; } + if (DebugFlags.WEB_TEXT_VIEW) { + Log.v(LOGTAG, "setText start=" + start + + " end=" + end); + } Selection.setSelection(span, start, end); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 186e1d1..e061a4c 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -329,9 +329,18 @@ public class WebView extends AbsoluteLayout /** * The minimum elapsed time before sending another ACTION_MOVE event to - * WebViewCore + * WebViewCore. This really should be tuned for each type of the devices. + * For example in Google Map api test case, it takes Dream device at least + * 150ms to do a full cycle in the WebViewCore by processing a touch event, + * triggering the layout and drawing the picture. While the same process + * takes 60+ms on the current high speed device. If we make + * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent + * to WebViewCore queue and the real layout and draw events will be pushed + * to further, which slows down the refresh rate. Choose 50 to favor the + * current high speed devices. For Dream like devices, 100 is a better + * choice. Maybe make this in the buildspec later. */ - private static final int TOUCH_SENT_INTERVAL = 100; + private static final int TOUCH_SENT_INTERVAL = 50; /** * Helper class to get velocity for fling @@ -449,6 +458,8 @@ public class WebView extends AbsoluteLayout static final int WEBCORE_INITIALIZED_MSG_ID = 16; static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17; static final int DID_FIRST_LAYOUT_MSG_ID = 18; + static final int MOVE_OUT_OF_PLUGIN = 19; + static final int CLEAR_TEXT_ENTRY = 20; static final int UPDATE_CLIPBOARD = 22; static final int LONG_PRESS_CENTER = 23; @@ -456,6 +467,7 @@ public class WebView extends AbsoluteLayout 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 String[] HandlerDebugString = { "REMEMBER_PASSWORD", // = 1; @@ -476,14 +488,15 @@ public class WebView extends AbsoluteLayout "WEBCORE_INITIALIZED_MSG_ID", // = 16; "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17; "DID_FIRST_LAYOUT_MSG_ID", // = 18; - "19", - "20", + "MOVE_OUT_OF_PLUGIN", // = 19; + "CLEAR_TEXT_ENTRY", // = 20; "21", // = 21; "UPDATE_CLIPBOARD", // = 22; "LONG_PRESS_CENTER", // = 23; "PREVENT_TOUCH_ID", // = 24; "WEBCORE_NEED_TOUCH_EVENTS", // = 25; - "INVAL_RECT_MSG_ID" // = 26; + "INVAL_RECT_MSG_ID", // = 26; + "REQUEST_KEYBOARD" // = 27; }; // width which view is considered to be fully zoomed out @@ -502,7 +515,7 @@ public class WebView extends AbsoluteLayout // default scale. Depending on the display density. static int DEFAULT_SCALE_PERCENT; - private float DEFAULT_SCALE; + private float mDefaultScale; // set to true temporarily while the zoom control is being dragged private boolean mPreviewZoomOnly = false; @@ -745,7 +758,7 @@ public class WebView extends AbsoluteLayout mNavSlop = (int) (16 * density); // density adjusted scale factors DEFAULT_SCALE_PERCENT = (int) (100 * density); - DEFAULT_SCALE = density; + mDefaultScale = density; mActualScale = density; mInvActualScale = 1 / density; DEFAULT_MAX_ZOOM_SCALE = 4.0f * density; @@ -754,6 +767,23 @@ public class WebView extends AbsoluteLayout mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; } + /* package */void updateDefaultZoomDensity(int zoomDensity) { + final float density = getContext().getResources().getDisplayMetrics().density + * 100 / zoomDensity; + if (Math.abs(density - mDefaultScale) > 0.01) { + float scaleFactor = density / mDefaultScale; + // adjust the limits + mNavSlop = (int) (16 * density); + DEFAULT_SCALE_PERCENT = (int) (100 * density); + DEFAULT_MAX_ZOOM_SCALE = 4.0f * density; + DEFAULT_MIN_ZOOM_SCALE = 0.25f * density; + mDefaultScale = density; + mMaxZoomScale *= scaleFactor; + mMinZoomScale *= scaleFactor; + setNewZoomScale(mActualScale * scaleFactor, false); + } + } + /* package */ boolean onSavePassword(String schemePlusHost, String username, String password, final Message resumeMsg) { boolean rVal = false; @@ -980,6 +1010,17 @@ public class WebView extends AbsoluteLayout } /** + * Sets JavaScript engine flags. + * + * @param flags JS engine flags in a String + * + * @hide pending API solidification + */ + public void setJsFlags(String flags) { + mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags); + } + + /** * Inform WebView of the network state. This is used to set * the javascript property window.navigator.isOnline and * generates the online/offline event as specified in HTML5, sec. 5.7.7 @@ -2318,6 +2359,16 @@ public class WebView extends AbsoluteLayout } /** + * Gets the chrome handler. + * @return the current WebChromeClient instance. + * + * @hide API council approval. + */ + public WebChromeClient getWebChromeClient() { + return mCallbackProxy.getWebChromeClient(); + } + + /** * Set the Picture listener. This is an interface used to receive * notifications of a new Picture. * @param listener An implementation of WebView.PictureListener. @@ -2454,6 +2505,14 @@ public class WebView extends AbsoluteLayout @Override 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()); + rebuildWebTextView(); + } if (inEditingMode()) { return mWebTextView.performLongClick(); } else { @@ -2964,24 +3023,37 @@ public class WebView extends AbsoluteLayout } // Called by JNI when a touch event puts a textfield into focus. - private void displaySoftKeyboard() { + private void displaySoftKeyboard(boolean isTextView) { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mWebTextView, 0); - mWebTextView.enableScrollOnScreen(true); - // Now we need to fake a touch event to place the cursor where the - // user touched. - AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) - mWebTextView.getLayoutParams(); - if (lp != null) { - // Take the last touch and adjust for the location of the - // WebTextView. - float x = mLastTouchX + (float) (mScrollX - lp.x); - float y = mLastTouchY + (float) (mScrollY - lp.y); - mWebTextView.fakeTouchEvent(x, y); + + if (isTextView) { + imm.showSoftInput(mWebTextView, 0); + // Now we need to fake a touch event to place the cursor where the + // user touched. + AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) + mWebTextView.getLayoutParams(); + if (lp != null) { + // Take the last touch and adjust for the location of the + // WebTextView. + float x = mLastTouchX + (float) (mScrollX - lp.x); + float y = mLastTouchY + (float) (mScrollY - lp.y); + mWebTextView.fakeTouchEvent(x, y); + } + } + else { // used by plugins + imm.showSoftInput(this, 0); } } + // Called by WebKit to instruct the UI to hide the keyboard + private void hideSoftKeyboard() { + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + imm.hideSoftInputFromWindow(this.getWindowToken(), 0); + } + /* * This method checks the current focus and cursor and potentially rebuilds * mWebTextView to have the appropriate properties, such as password, @@ -3017,8 +3089,7 @@ public class WebView extends AbsoluteLayout // should be in content coordinates. Rect bounds = nativeFocusCandidateNodeBounds(); if (!Rect.intersects(bounds, visibleRect)) { - // Node is not on screen, so do not bother. - return; + mWebTextView.bringIntoView(); } String text = nativeFocusCandidateText(); int nodePointer = nativeFocusCandidatePointer(); @@ -3072,6 +3143,9 @@ public class WebView extends AbsoluteLayout mWebTextView.setInPassword(nativeFocusCandidateIsPassword()); 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 @@ -3086,8 +3160,14 @@ public class WebView extends AbsoluteLayout } else if (isTextField) { int length = text.length(); mWebTextView.setText(text, length, length); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "rebuildWebTextView length=" + length); + } } else { mWebTextView.setText(text, 0, 0); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "rebuildWebTextView !isTextField"); + } } } mWebTextView.requestFocus(); @@ -3129,7 +3209,7 @@ public class WebView extends AbsoluteLayout public boolean onKeyDown(int keyCode, KeyEvent event) { if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() - + ", " + event); + + ", " + event + ", unicode=" + event.getUnicodeChar()); } if (mNativeClass == 0) { @@ -3175,7 +3255,7 @@ public class WebView extends AbsoluteLayout && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { // always handle the navigation keys in the UI thread switchOutDrawHistory(); - if (navHandledKey(keyCode, 1, false, event.getEventTime())) { + if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) { playSoundEffect(keyCodeToSoundsEffect(keyCode)); return true; } @@ -3235,15 +3315,23 @@ public class WebView extends AbsoluteLayout } } - if (nativeCursorWantsKeyEvents() && !nativeCursorMatchesFocus()) { + if (nativeCursorIsPlugin()) { + nativeUpdatePluginReceivesEvents(); + invalidate(); + } else if (nativeCursorIsTextInput()) { // This message will put the node in focus, for the DOM's notion - // of focus + // of focus, and make the focuscontroller active mWebViewCore.sendMessage(EventHub.CLICK); - if (nativeCursorIsTextInput()) { - // This will bring up the WebTextView and put it in focus, for - // our view system's notion of focus - rebuildWebTextView(); - // Now we need to pass the event to it + // This will bring up the WebTextView and put it in focus, for + // our view system's notion of focus + rebuildWebTextView(); + // Now we need to pass the event to it + return mWebTextView.onKeyDown(keyCode, 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); } } @@ -3264,7 +3352,7 @@ public class WebView extends AbsoluteLayout public boolean onKeyUp(int keyCode, KeyEvent event) { if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() - + ", " + event); + + ", " + event + ", unicode=" + event.getUnicodeChar()); } if (mNativeClass == 0) { @@ -3409,9 +3497,7 @@ public class WebView extends AbsoluteLayout public void onChildViewRemoved(View p, View child) { if (child == this) { - if (inEditingMode()) { - clearTextEntry(); - } + clearTextEntry(); } } @@ -3435,6 +3521,9 @@ public class WebView extends AbsoluteLayout mDrawCursorRing = true; if (mNativeClass != 0) { nativeRecordButtons(true, false, true); + if (inEditingMode()) { + mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0); + } } } else { // If our window gained focus, but we do not have it, do not @@ -3477,7 +3566,7 @@ public class WebView extends AbsoluteLayout // Do not need to also check whether mWebViewCore is null, because // mNativeClass is only set if mWebViewCore is non null if (mNativeClass == 0) return; - mWebViewCore.sendMessage(EventHub.SET_INACTIVE); + mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0); } @Override @@ -3947,9 +4036,10 @@ public class WebView extends AbsoluteLayout if (ev.getAction() == MotionEvent.ACTION_DOWN) { mPrivateHandler.removeMessages(SWITCH_TO_CLICK); mTrackballDown = true; - if (mNativeClass != 0) { - nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); + if (mNativeClass == 0) { + return false; } + nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); if (time - mLastCursorTime <= TRACKBALL_TIMEOUT && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { nativeSelectBestAt(mLastCursorBounds); @@ -4162,7 +4252,7 @@ public class WebView extends AbsoluteLayout + " mTrackballRemainsX=" + mTrackballRemainsX + " mTrackballRemainsY=" + mTrackballRemainsY); } - if (navHandledKey(selectKeyCode, count, false, time)) { + if (navHandledKey(selectKeyCode, count, false, time, false)) { playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); } mTrackballRemainsX = mTrackballRemainsY = 0; @@ -4238,8 +4328,8 @@ public class WebView extends AbsoluteLayout float oldScale = mActualScale; // snap to DEFAULT_SCALE if it is close - if (scale > (DEFAULT_SCALE - 0.05) && scale < (DEFAULT_SCALE + 0.05)) { - scale = DEFAULT_SCALE; + if (scale > (mDefaultScale - 0.05) && scale < (mDefaultScale + 0.05)) { + scale = mDefaultScale; } setNewZoomScale(scale, false); @@ -4444,7 +4534,7 @@ public class WebView extends AbsoluteLayout return result; } if (mNativeClass != 0 && !nativeHasCursorNode()) { - navHandledKey(fakeKeyDirection, 1, true, 0); + navHandledKey(fakeKeyDirection, 1, true, 0, true); } } } @@ -4622,9 +4712,11 @@ public class WebView extends AbsoluteLayout break; } case SWITCH_TO_LONGPRESS: { - mTouchMode = TOUCH_DONE_MODE; - performLongClick(); - rebuildWebTextView(); + if (!mPreventDrag) { + mTouchMode = TOUCH_DONE_MODE; + performLongClick(); + rebuildWebTextView(); + } break; } case SWITCH_TO_CLICK: @@ -4639,13 +4731,15 @@ public class WebView extends AbsoluteLayout break; } nativeSetFollowedLink(true); - mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, - cursorData()); + nativeUpdatePluginReceivesEvents(); + WebViewCore.CursorData data = cursorData(); + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); playSoundEffect(SoundEffectConstants.CLICK); boolean isTextInput = nativeCursorIsTextInput(); if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading( nativeCursorText())) { - mWebViewCore.sendMessage(EventHub.CLICK); + mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, + nativeCursorNodePointer()); } if (isTextInput) { rebuildWebTextView(); @@ -4769,7 +4863,7 @@ public class WebView extends AbsoluteLayout int initialScale = msg.arg1; int viewportWidth = msg.arg2; // start a new page with DEFAULT_SCALE zoom scale. - float scale = DEFAULT_SCALE; + float scale = mDefaultScale; if (mInitialScale > 0) { scale = mInitialScale / 100.0f; } else { @@ -4791,6 +4885,11 @@ public class WebView extends AbsoluteLayout } setNewZoomScale(scale, false); break; + case MOVE_OUT_OF_PLUGIN: + if (nativePluginEatsNavKey()) { + 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. @@ -4799,6 +4898,9 @@ public class WebView extends AbsoluteLayout } rebuildWebTextView(); break; + case CLEAR_TEXT_ENTRY: + clearTextEntry(); + break; case INVAL_RECT_MSG_ID: { Rect r = (Rect)msg.obj; if (r == null) { @@ -4860,6 +4962,14 @@ public class WebView extends AbsoluteLayout } break; + case REQUEST_KEYBOARD: + if (msg.arg1 == 0) { + hideSoftKeyboard(); + } else { + displaySoftKeyboard(false); + } + break; + default: super.handleMessage(msg); break; @@ -5118,12 +5228,23 @@ public class WebView extends AbsoluteLayout new WebViewCore.CursorData(frame, node, x, y)); } - // called by JNI - private void sendMoveMouseIfLatest(boolean setFocusControllerInactive) { - if (setFocusControllerInactive) { + /* + * Send a mouse move event to the webcore thread. + * + * @param removeFocus Pass true if the "mouse" cursor is now over a node + * which wants key events, but it is not the focus. This + * will make the visual appear as though nothing is in + * focus. Remove the WebTextView, if present, and stop + * drawing the blinking caret. + * called by JNI + */ + private void sendMoveMouseIfLatest(boolean removeFocus) { + if (removeFocus) { + clearTextEntry(); setFocusControllerInactive(); } - mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, cursorData()); + mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, + cursorData()); } // called by JNI @@ -5176,11 +5297,21 @@ public class WebView extends AbsoluteLayout } // return true if the key was handled - private boolean navHandledKey(int keyCode, int count, boolean noScroll - , long time) { + private boolean navHandledKey(int keyCode, int count, boolean noScroll, + long time, boolean ignorePlugin) { if (mNativeClass == 0) { return false; } + if (ignorePlugin == false && nativePluginEatsNavKey()) { + KeyEvent event = new KeyEvent(time, time, KeyEvent.ACTION_DOWN + , keyCode, count, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0) + | (false ? KeyEvent.META_ALT_ON : 0) // FIXME + | (false ? KeyEvent.META_SYM_ON : 0) // FIXME + , 0, 0, 0); + mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); + mWebViewCore.sendMessage(EventHub.KEY_UP, event); + return true; + } mLastCursorTime = time; mLastCursorBounds = nativeGetCursorRingBounds(); boolean keyHandled @@ -5263,6 +5394,7 @@ public class WebView extends AbsoluteLayout /* 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(); @@ -5286,7 +5418,7 @@ public class WebView extends AbsoluteLayout private native boolean nativeFocusCandidateIsTextField(); private native boolean nativeFocusCandidateIsTextInput(); private native int nativeFocusCandidateMaxLength(); - private native String nativeFocusCandidateName(); + /* package */ native String nativeFocusCandidateName(); private native Rect nativeFocusCandidateNodeBounds(); /* package */ native int nativeFocusCandidatePointer(); private native String nativeFocusCandidateText(); @@ -5306,6 +5438,7 @@ public class WebView extends AbsoluteLayout private native int nativeMoveGeneration(); private native void nativeMoveSelection(int x, int y, boolean extendSelection); + private native boolean nativePluginEatsNavKey(); // 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, @@ -5319,5 +5452,5 @@ public class WebView extends AbsoluteLayout // we always want to pass in our generation number. private native void nativeUpdateCachedTextfield(String updatedText, int generation); - + private native void nativeUpdatePluginReceivesEvents(); } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 4ad9a1a..d8d9808 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -136,7 +136,7 @@ final class WebViewCore { // ready. mEventHub = new EventHub(); // Create a WebSettings object for maintaining all settings - mSettings = new WebSettings(mContext); + mSettings = new WebSettings(mContext, mWebView); // The WebIconDatabase needs to be initialized within the UI thread so // just request the instance here. WebIconDatabase.getInstance(); @@ -346,9 +346,10 @@ final class WebViewCore { private native void nativeSplitContent(); private native boolean nativeKey(int keyCode, int unichar, - int repeatCount, boolean isShift, boolean isAlt, boolean isDown); + int repeatCount, boolean isShift, boolean isAlt, boolean isSym, + boolean isDown); - private native boolean nativeClick(); + private native void nativeClick(int framePtr, int nodePtr); private native void nativeSendListBoxChoices(boolean[] choices, int size); @@ -375,7 +376,7 @@ final class WebViewCore { String currentText, int keyCode, int keyValue, boolean down, boolean cap, boolean fn, boolean sym); - private native void nativeSetFocusControllerInactive(); + private native void nativeSetFocusControllerActive(boolean active); private native void nativeSaveDocumentState(int frame); @@ -406,6 +407,8 @@ final class WebViewCore { private native void nativeDumpNavTree(); + private native void nativeSetJsFlags(String flags); + /** * Delete text from start to end in the focused textfield. If there is no * focus, or if start == end, silently fail. If start and end are out of @@ -610,7 +613,7 @@ final class WebViewCore { "LOAD_DATA", // = 139; "TOUCH_UP", // = 140; "TOUCH_EVENT", // = 141; - "SET_INACTIVE", // = 142; + "SET_ACTIVE", // = 142; "ON_PAUSE", // = 143 "ON_RESUME", // = 144 "FREE_MEMORY", // = 145 @@ -668,7 +671,7 @@ final class WebViewCore { // Used to tell the focus controller not to draw the blinking cursor, // based on whether the WebView has focus and whether the WebView's // cursor matches the webpage's focus. - static final int SET_INACTIVE = 142; + static final int SET_ACTIVE = 142; // lifecycle activities for just this DOM (unlike pauseTimers, which // is global) @@ -688,6 +691,8 @@ final class WebViewCore { static final int DUMP_RENDERTREE = 171; static final int DUMP_NAVTREE = 172; + static final int SET_JS_FLAGS = 173; + // private message ids private static final int DESTROY = 200; @@ -719,9 +724,11 @@ final class WebViewCore { @Override public void handleMessage(Message msg) { if (DebugFlags.WEB_VIEW_CORE) { - Log.v(LOGTAG, msg.what < LOAD_URL || msg.what + Log.v(LOGTAG, (msg.what < LOAD_URL || msg.what > FREE_MEMORY ? Integer.toString(msg.what) - : HandlerDebugString[msg.what - LOAD_URL]); + : HandlerDebugString[msg.what - LOAD_URL]) + + " arg1=" + msg.arg1 + " arg2=" + msg.arg2 + + " obj=" + msg.obj); } switch (msg.what) { case WEBKIT_DRAW: @@ -803,7 +810,7 @@ final class WebViewCore { break; case CLICK: - nativeClick(); + nativeClick(msg.arg1, msg.arg2); break; case VIEW_SIZE_CHANGED: @@ -947,8 +954,8 @@ final class WebViewCore { break; } - case SET_INACTIVE: - nativeSetFocusControllerInactive(); + case SET_ACTIVE: + nativeSetFocusControllerActive(msg.arg1 == 1); break; case ADD_JS_INTERFACE: @@ -1054,6 +1061,10 @@ final class WebViewCore { nativeDumpNavTree(); break; + case SET_JS_FLAGS: + nativeSetJsFlags((String)msg.obj); + break; + case SYNC_SCROLL: mWebkitScrollX = msg.arg1; mWebkitScrollY = msg.arg2; @@ -1260,7 +1271,19 @@ final class WebViewCore { int keyCode = evt.getKeyCode(); if (!nativeKey(keyCode, evt.getUnicodeChar(), evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(), + evt.isSymPressed(), isDown) && keyCode != KeyEvent.KEYCODE_ENTER) { + if (keyCode >= KeyEvent.KEYCODE_DPAD_UP + && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { + if (DebugFlags.WEB_VIEW_CORE) { + Log.v(LOGTAG, "key: arrow unused by plugin: " + keyCode); + } + if (mWebView != null && evt.isDown()) { + Message.obtain(mWebView.mPrivateHandler, + WebView.MOVE_OUT_OF_PLUGIN, keyCode).sendToTarget(); + } + return; + } // bubble up the event handling // but do not bubble up the ENTER key, which would open the search // bar without any text. @@ -1618,6 +1641,20 @@ final class WebViewCore { // set the viewport settings from WebKit setViewportSettingsFromNative(); + // adjust the default scale to match the density + if (WebView.DEFAULT_SCALE_PERCENT != 100) { + float adjust = (float) WebView.DEFAULT_SCALE_PERCENT / 100.0f; + if (mViewportInitialScale > 0) { + mViewportInitialScale *= adjust; + } + if (mViewportMinimumScale > 0) { + mViewportMinimumScale *= adjust; + } + if (mViewportMaximumScale > 0) { + mViewportMaximumScale *= adjust; + } + } + // infer the values if they are not defined. if (mViewportWidth == 0) { if (mViewportInitialScale == 0) { @@ -1722,6 +1759,13 @@ final class WebViewCore { } } + // called by JNI + private void clearTextEntry() { + if (mWebView == null) return; + Message.obtain(mWebView.mPrivateHandler, + WebView.CLEAR_TEXT_ENTRY).sendToTarget(); + } + // these must be in document space (i.e. not scaled/zoomed). private native void nativeSetScrollOffset(int gen, int dx, int dy); @@ -1744,6 +1788,15 @@ final class WebViewCore { } + // called by JNI + private void requestKeyboard(boolean showKeyboard) { + if (mWebView != null) { + Message.obtain(mWebView.mPrivateHandler, + WebView.REQUEST_KEYBOARD, showKeyboard ? 1 : 0, 0) + .sendToTarget(); + } + } + private native void nativePause(); private native void nativeResume(); private native void nativeFreeMemory(); diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 1004e30..4e76254 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -48,7 +48,9 @@ public class WebViewDatabase { // 6 -> 7 Change cache localPath from int to String // 7 -> 8 Move cache to its own db // 8 -> 9 Store both scheme and host when storing passwords - private static final int CACHE_DATABASE_VERSION = 1; + private static final int CACHE_DATABASE_VERSION = 3; + // 1 -> 2 Add expires String + // 2 -> 3 Add content-disposition private static WebViewDatabase mInstance = null; @@ -107,6 +109,8 @@ public class WebViewDatabase { private static final String CACHE_EXPIRES_COL = "expires"; + private static final String CACHE_EXPIRES_STRING_COL = "expiresstring"; + private static final String CACHE_MIMETYPE_COL = "mimetype"; private static final String CACHE_ENCODING_COL = "encoding"; @@ -117,6 +121,8 @@ public class WebViewDatabase { private static final String CACHE_CONTENTLENGTH_COL = "contentlength"; + private static final String CACHE_CONTENTDISPOSITION_COL = "contentdisposition"; + // column id strings for "password" table private static final String PASSWORD_HOST_COL = "host"; @@ -150,11 +156,13 @@ public class WebViewDatabase { private static int mCacheLastModifyColIndex; private static int mCacheETagColIndex; private static int mCacheExpiresColIndex; + private static int mCacheExpiresStringColIndex; private static int mCacheMimeTypeColIndex; private static int mCacheEncodingColIndex; private static int mCacheHttpStatusColIndex; private static int mCacheLocationColIndex; private static int mCacheContentLengthColIndex; + private static int mCacheContentDispositionColIndex; private static int mCacheTransactionRefcount; @@ -220,6 +228,8 @@ public class WebViewDatabase { .getColumnIndex(CACHE_ETAG_COL); mCacheExpiresColIndex = mCacheInserter .getColumnIndex(CACHE_EXPIRES_COL); + mCacheExpiresStringColIndex = mCacheInserter + .getColumnIndex(CACHE_EXPIRES_STRING_COL); mCacheMimeTypeColIndex = mCacheInserter .getColumnIndex(CACHE_MIMETYPE_COL); mCacheEncodingColIndex = mCacheInserter @@ -230,6 +240,8 @@ public class WebViewDatabase { .getColumnIndex(CACHE_LOCATION_COL); mCacheContentLengthColIndex = mCacheInserter .getColumnIndex(CACHE_CONTENTLENGTH_COL); + mCacheContentDispositionColIndex = mCacheInserter + .getColumnIndex(CACHE_CONTENTDISPOSITION_COL); } } @@ -320,11 +332,12 @@ public class WebViewDatabase { + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, " + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, " + + CACHE_EXPIRES_STRING_COL + " TEXT, " + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, " + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL - + " INTEGER, " + " UNIQUE (" + CACHE_URL_COL - + ") ON CONFLICT REPLACE);"); + + " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, " + + " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);"); mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache (" + CACHE_URL_COL + ")"); } @@ -537,8 +550,8 @@ public class WebViewDatabase { } Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, " - + "mimetype, encoding, httpstatus, location, contentlength " - + "FROM cache WHERE url = ?", + + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, " + + "contentdisposition FROM cache WHERE url = ?", new String[] { url }); try { @@ -548,11 +561,13 @@ public class WebViewDatabase { ret.lastModified = cursor.getString(1); ret.etag = cursor.getString(2); ret.expires = cursor.getLong(3); - ret.mimeType = cursor.getString(4); - ret.encoding = cursor.getString(5); - ret.httpStatusCode = cursor.getInt(6); - ret.location = cursor.getString(7); - ret.contentLength = cursor.getLong(8); + ret.expiresString = cursor.getString(4); + ret.mimeType = cursor.getString(5); + ret.encoding = cursor.getString(6); + ret.httpStatusCode = cursor.getInt(7); + ret.location = cursor.getString(8); + ret.contentLength = cursor.getLong(9); + ret.contentdisposition = cursor.getString(10); return ret; } } finally { @@ -591,11 +606,14 @@ public class WebViewDatabase { mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified); mCacheInserter.bind(mCacheETagColIndex, c.etag); mCacheInserter.bind(mCacheExpiresColIndex, c.expires); + mCacheInserter.bind(mCacheExpiresStringColIndex, c.expiresString); mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType); mCacheInserter.bind(mCacheEncodingColIndex, c.encoding); mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode); mCacheInserter.bind(mCacheLocationColIndex, c.location); mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength); + mCacheInserter.bind(mCacheContentDispositionColIndex, + c.contentdisposition); mCacheInserter.execute(); } diff --git a/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java b/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java index 74d27ed..b3d7f69 100644 --- a/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java +++ b/core/java/android/webkit/gears/ApacheHttpRequestAndroid.java @@ -38,7 +38,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import java.lang.StringBuilder; -import java.util.Date; import java.util.Map; import java.util.HashMap; import java.util.Iterator; @@ -57,7 +56,6 @@ import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.conn.ssl.StrictHostnameVerifier; -import org.apache.http.impl.cookie.DateUtils; import org.apache.http.util.CharArrayBuffer; import java.util.concurrent.locks.Condition; @@ -863,12 +861,9 @@ public final class ApacheHttpRequestAndroid { mResponseHeaders = new HashMap<String, String[]>(); String contentLength = Long.toString(cacheResult.getContentLength()); setResponseHeader(KEY_CONTENT_LENGTH, contentLength); - long expires = cacheResult.getExpires(); - if (expires >= 0) { - // "Expires" header is valid and finite. Milliseconds since 1970 - // epoch, formatted as RFC-1123. - String expiresString = DateUtils.formatDate(new Date(expires)); - setResponseHeader(KEY_EXPIRES, expiresString); + String expires = cacheResult.getExpiresString(); + if (expires != null) { + setResponseHeader(KEY_EXPIRES, expires); } String lastModified = cacheResult.getLastModified(); if (lastModified != null) { diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 96cc2fd..ba47df5 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -1185,7 +1185,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe int position, long id) { if (position != -1) { - mDropDownList.mListSelectionHidden = false; + DropDownListView dropDownList = mDropDownList; + + if (dropDownList != null) { + dropDownList.mListSelectionHidden = false; + } } } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 441414a..2c9e71e 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.StateListDrawable; +import android.graphics.drawable.Animatable; import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.Shape; import android.util.AttributeSet; @@ -683,7 +684,7 @@ public class ProgressBar extends View { return; } - if (mIndeterminateDrawable instanceof AnimationDrawable) { + if (mIndeterminateDrawable instanceof Animatable) { mShouldStartAnimationDrawable = true; mAnimation = null; } else { @@ -708,8 +709,8 @@ public class ProgressBar extends View { void stopAnimation() { mAnimation = null; mTransformation = null; - if (mIndeterminateDrawable instanceof AnimationDrawable) { - ((AnimationDrawable) mIndeterminateDrawable).stop(); + if (mIndeterminateDrawable instanceof Animatable) { + ((Animatable) mIndeterminateDrawable).stop(); mShouldStartAnimationDrawable = false; } } @@ -818,8 +819,8 @@ public class ProgressBar extends View { } d.draw(canvas); canvas.restore(); - if (mShouldStartAnimationDrawable && d instanceof AnimationDrawable) { - ((AnimationDrawable) d).start(); + if (mShouldStartAnimationDrawable && d instanceof Animatable) { + ((Animatable) d).start(); mShouldStartAnimationDrawable = false; } } diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 955475e..e62dda5 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -41,6 +41,7 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.LinkedList; import java.util.HashSet; +import java.util.ArrayList; /** * A Layout where the positions of the children can be described in relation to each other or to the @@ -339,11 +340,17 @@ public class RelativeLayout extends ViewGroup { int right = Integer.MIN_VALUE; int bottom = Integer.MIN_VALUE; + boolean offsetHorizontalAxis = false; + boolean offsetVerticalAxis = false; + if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) { ignore = findViewById(mIgnoreGravity); } - View[] views = mSortedVerticalChildren; + final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY; + final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY; + + View[] views = mSortedHorizontalChildren; int count = views.length; for (int i = 0; i < count; i++) { View child = views[i]; @@ -351,13 +358,16 @@ public class RelativeLayout extends ViewGroup { LayoutParams params = (LayoutParams) child.getLayoutParams(); applyHorizontalSizeRules(params, myWidth); - measureChildHorizontal(child, params, myWidth); - positionChildHorizontal(child, params, myWidth); + measureChildHorizontal(child, params, myWidth, myHeight); + if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) { + offsetHorizontalAxis = true; + } } } - views = mSortedHorizontalChildren; + views = mSortedVerticalChildren; count = views.length; + for (int i = 0; i < count; i++) { View child = views[i]; if (child.getVisibility() != GONE) { @@ -365,12 +375,15 @@ public class RelativeLayout extends ViewGroup { applyVerticalSizeRules(params, myHeight); measureChild(child, params, myWidth, myHeight); - positionChildVertical(child, params, myHeight); + if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) { + offsetVerticalAxis = true; + } - if (widthMode != MeasureSpec.EXACTLY) { + if (isWrapContentWidth) { width = Math.max(width, params.mRight); } - if (heightMode != MeasureSpec.EXACTLY) { + + if (isWrapContentHeight) { height = Math.max(height, params.mBottom); } @@ -406,7 +419,7 @@ public class RelativeLayout extends ViewGroup { } } - if (widthMode != MeasureSpec.EXACTLY) { + if (isWrapContentWidth) { // Width already has left padding in it since it was calculated by looking at // the right of each child view width += mPaddingRight; @@ -417,8 +430,22 @@ public class RelativeLayout extends ViewGroup { width = Math.max(width, getSuggestedMinimumWidth()); width = resolveSize(width, widthMeasureSpec); + + if (offsetHorizontalAxis) { + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + LayoutParams params = (LayoutParams) child.getLayoutParams(); + final int[] rules = params.getRules(); + if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) { + centerHorizontal(child, params, width); + } + } + } + } } - if (heightMode != MeasureSpec.EXACTLY) { + + if (isWrapContentHeight) { // Height already has top padding in it since it was calculated by looking at // the bottom of each child view height += mPaddingBottom; @@ -429,6 +456,19 @@ public class RelativeLayout extends ViewGroup { height = Math.max(height, getSuggestedMinimumHeight()); height = resolveSize(height, heightMeasureSpec); + + if (offsetVerticalAxis) { + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + LayoutParams params = (LayoutParams) child.getLayoutParams(); + final int[] rules = params.getRules(); + if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { + centerVertical(child, params, height); + } + } + } + } } if (horizontalGravity || verticalGravity) { @@ -510,13 +550,18 @@ public class RelativeLayout extends ViewGroup { child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } - private void measureChildHorizontal(View child, LayoutParams params, int myWidth) { + private void measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight) { int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight, params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight, myWidth); - int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + int childHeightMeasureSpec; + if (params.width == LayoutParams.FILL_PARENT) { + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY); + } else { + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST); + } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } @@ -599,7 +644,9 @@ public class RelativeLayout extends ViewGroup { return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode); } - private void positionChildHorizontal(View child, LayoutParams params, int myWidth) { + private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth, + boolean wrapContent) { + int[] rules = params.getRules(); if (params.mLeft < 0 && params.mRight >= 0) { @@ -610,16 +657,25 @@ public class RelativeLayout extends ViewGroup { params.mRight = params.mLeft + child.getMeasuredWidth(); } else if (params.mLeft < 0 && params.mRight < 0) { // Both left and right vary - if (0 != rules[CENTER_IN_PARENT] || 0 != rules[CENTER_HORIZONTAL]) { - centerHorizontal(child, params, myWidth); + if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) { + if (!wrapContent) { + centerHorizontal(child, params, myWidth); + } else { + params.mLeft = mPaddingLeft + params.leftMargin; + params.mRight = params.mLeft + child.getMeasuredWidth(); + } + return true; } else { params.mLeft = mPaddingLeft + params.leftMargin; params.mRight = params.mLeft + child.getMeasuredWidth(); } } + return false; } - private void positionChildVertical(View child, LayoutParams params, int myHeight) { + private boolean positionChildVertical(View child, LayoutParams params, int myHeight, + boolean wrapContent) { + int[] rules = params.getRules(); if (params.mTop < 0 && params.mBottom >= 0) { @@ -630,13 +686,20 @@ public class RelativeLayout extends ViewGroup { params.mBottom = params.mTop + child.getMeasuredHeight(); } else if (params.mTop < 0 && params.mBottom < 0) { // Both top and bottom vary - if (0 != rules[CENTER_IN_PARENT] || 0 != rules[CENTER_VERTICAL]) { - centerVertical(child, params, myHeight); + if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { + if (!wrapContent) { + centerVertical(child, params, myHeight); + } else { + params.mTop = mPaddingTop + params.topMargin; + params.mBottom = params.mTop + child.getMeasuredHeight(); + } + return true; } else { params.mTop = mPaddingTop + params.topMargin; params.mBottom = params.mTop + child.getMeasuredHeight(); } } + return false; } private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) { @@ -766,14 +829,14 @@ public class RelativeLayout extends ViewGroup { private View getRelatedView(int[] rules, int relation) { int id = rules[relation]; if (id != 0) { - DependencyGraph.Node node = mGraph.mNodes.get(id); + DependencyGraph.Node node = mGraph.mKeyNodes.get(id); if (node == null) return null; View v = node.view; // Find the first non-GONE view up the chain while (v.getVisibility() == View.GONE) { rules = ((LayoutParams) v.getLayoutParams()).getRules(); - node = mGraph.mNodes.get((rules[relation])); + node = mGraph.mKeyNodes.get((rules[relation])); if (node == null) return null; v = node.view; } @@ -1109,10 +1172,15 @@ public class RelativeLayout extends ViewGroup { private static class DependencyGraph { /** + * List of all views in the graph. + */ + private ArrayList<Node> mNodes = new ArrayList<Node>(); + + /** * List of nodes in the graph. Each node is identified by its * view id (see View#getId()). */ - private SparseArray<Node> mNodes = new SparseArray<Node>(); + private SparseArray<Node> mKeyNodes = new SparseArray<Node>(); /** * Temporary data structure used to build the list of roots @@ -1124,14 +1192,15 @@ public class RelativeLayout extends ViewGroup { * Clears the graph. */ void clear() { - final SparseArray<Node> nodes = mNodes; + final ArrayList<Node> nodes = mNodes; final int count = nodes.size(); for (int i = 0; i < count; i++) { - nodes.valueAt(i).release(); + nodes.get(i).release(); } nodes.clear(); + mKeyNodes.clear(); mRoots.clear(); } @@ -1141,7 +1210,14 @@ public class RelativeLayout extends ViewGroup { * @param view The view to be added as a node to the graph. */ void add(View view) { - mNodes.put(view.getId(), Node.acquire(view)); + final int id = view.getId(); + final Node node = Node.acquire(view); + + if (id != View.NO_ID) { + mKeyNodes.put(id, node); + } + + mNodes.add(node); } /** @@ -1192,20 +1268,21 @@ public class RelativeLayout extends ViewGroup { * @return A list of node, each being a root of the graph */ private LinkedList<Node> findRoots(int[] rulesFilter) { - final SparseArray<Node> nodes = mNodes; + final SparseArray<Node> keyNodes = mKeyNodes; + final ArrayList<Node> nodes = mNodes; final int count = nodes.size(); // Find roots can be invoked several times, so make sure to clear // all dependents and dependencies before running the algorithm for (int i = 0; i < count; i++) { - final Node node = nodes.valueAt(i); + final Node node = nodes.get(i); node.dependents.clear(); node.dependencies.clear(); } // Builds up the dependents and dependencies for each node of the graph for (int i = 0; i < count; i++) { - final Node node = nodes.valueAt(i); + final Node node = nodes.get(i); final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams(); final int[] rules = layoutParams.mRules; @@ -1217,11 +1294,14 @@ public class RelativeLayout extends ViewGroup { final int rule = rules[rulesFilter[j]]; if (rule > 0) { // The node this node depends on - final Node dependency = nodes.get(rule); + final Node dependency = keyNodes.get(rule); if (dependency == node) { throw new IllegalStateException("A view cannot have a dependency" + " on itself"); } + if (dependency == null) { + continue; + } // Add the current node as a dependent dependency.dependents.add(node); // Add a dependency to the current node @@ -1235,7 +1315,7 @@ public class RelativeLayout extends ViewGroup { // Finds all the roots in the graph: all nodes with no dependencies for (int i = 0; i < count; i++) { - final Node node = nodes.valueAt(i); + final Node node = nodes.get(i); if (node.dependencies.size() == 0) roots.add(node); } @@ -1323,7 +1403,9 @@ public class RelativeLayout extends ViewGroup { /* * START POOL IMPLEMENTATION */ - private static final int POOL_LIMIT = 12; + // The pool is static, so all nodes instances are shared across + // activities, that's why we give it a rather high limit + private static final int POOL_LIMIT = 100; private static final Pool<Node> sPool = Pools.synchronizedPool( Pools.finitePool(new PoolableManager<Node>() { public Node newInstance() { diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index ec63528..4bef265 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -41,6 +41,20 @@ interface IBackupTransport { - adb: close the file */ /** + * Ask the transport where, on local device storage, to keep backup state blobs. + * This is per-transport so that mock transports used for testing can coexist with + * "live" backup services without interfering with the live bookkeeping. The + * returned string should be a name that is expected to be unambiguous among all + * available backup transports; the name of the class implementing the transport + * is a good choice. + * + * @return A unique name, suitable for use as a file or directory name, that the + * Backup Manager could use to disambiguate state files associated with + * different backup transports. + */ + String transportDirName(); + + /** * Verify that this is a suitable time for a backup pass. This should return zero * if a backup is reasonable right now, some positive value otherwise. This method * will be called outside of the {@link #startSession}/{@link #endSession} pair. diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 0fbbb3f..c5d9d40 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -30,6 +30,9 @@ public class LocalTransport extends IBackupTransport.Stub { private static final String TAG = "LocalTransport"; private static final boolean DEBUG = true; + private static final String TRANSPORT_DIR_NAME + = "com.android.internal.backup.LocalTransport"; + private Context mContext; private PackageManager mPackageManager; private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); @@ -43,6 +46,11 @@ public class LocalTransport extends IBackupTransport.Stub { mPackageManager = context.getPackageManager(); } + + public String transportDirName() throws RemoteException { + return TRANSPORT_DIR_NAME; + } + public long requestBackupTime() throws RemoteException { // any time is a good time for local backup return 0; diff --git a/core/java/com/android/internal/backup/SystemBackupAgent.java b/core/java/com/android/internal/backup/SystemBackupAgent.java new file mode 100644 index 0000000..6b396d7 --- /dev/null +++ b/core/java/com/android/internal/backup/SystemBackupAgent.java @@ -0,0 +1,35 @@ +/* + * 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.backup; + +import android.backup.AbsoluteFileBackupHelper; +import android.backup.BackupHelperAgent; + +/** + * Backup agent for various system-managed data + */ +public class SystemBackupAgent extends BackupHelperAgent { + // the set of files that we back up whole, as absolute paths + String[] mFiles = { + /* WallpaperService.WALLPAPER_FILE */ + "/data/data/com.android.settings/files/wallpaper", + }; + + public void onCreate() { + addHelper("system_files", new AbsoluteFileBackupHelper(this, mFiles)); + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index fc4a9c4..a03802d 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -2836,14 +2836,12 @@ public final class BatteryStatsImpl extends BatteryStats { * @param name process name * @return the statistics object for the process */ - public Uid.Proc getProcessStatsLocked(String name) { + public Uid.Proc getProcessStatsLocked(String name, int pid) { int uid; if (mUidCache.containsKey(name)) { uid = mUidCache.get(name); } else { - // TODO: Find the actual uid from /proc/pid/status. For now use the hashcode of the - // process name - uid = name.hashCode(); + uid = Process.getUidForPid(pid); mUidCache.put(name, uid); } Uid u = getUidStatsLocked(uid); diff --git a/core/java/com/android/internal/util/BitwiseInputStream.java b/core/java/com/android/internal/util/BitwiseInputStream.java index 4757919..86f74f3 100644 --- a/core/java/com/android/internal/util/BitwiseInputStream.java +++ b/core/java/com/android/internal/util/BitwiseInputStream.java @@ -65,30 +65,31 @@ public class BitwiseInputStream { /** * Read some data and increment the current position. * - * @param bits the amount of data to read (gte 0, lte 8) + * The 8-bit limit on access to bitwise streams is intentional to + * avoid endianness issues. * + * @param bits the amount of data to read (gte 0, lte 8) * @return byte of read data (possibly partially filled, from lsb) */ - public byte read(int bits) throws AccessException { + public int read(int bits) throws AccessException { int index = mPos >>> 3; int offset = 16 - (mPos & 0x07) - bits; // &7==%8 if ((bits < 0) || (bits > 8) || ((mPos + bits) > mEnd)) { throw new AccessException("illegal read " + "(pos " + mPos + ", end " + mEnd + ", bits " + bits + ")"); } - int data = (mBuf[index] & 0x00FF) << 8; - if (offset < 8) data |= (mBuf[index + 1] & 0xFF); + int data = (mBuf[index] & 0xFF) << 8; + if (offset < 8) data |= mBuf[index + 1] & 0xFF; data >>>= offset; data &= (-1 >>> (32 - bits)); mPos += bits; - return (byte)data; + return data; } /** * Read data in bulk into a byte array and increment the current position. * * @param bits the amount of data to read - * * @return newly allocated byte array of read data */ public byte[] readByteArray(int bits) throws AccessException { diff --git a/core/java/com/android/internal/util/BitwiseOutputStream.java b/core/java/com/android/internal/util/BitwiseOutputStream.java index 1b974ce..70c0be8 100644 --- a/core/java/com/android/internal/util/BitwiseOutputStream.java +++ b/core/java/com/android/internal/util/BitwiseOutputStream.java @@ -82,6 +82,9 @@ public class BitwiseOutputStream { /** * Write some data and increment the current position. * + * The 8-bit limit on access to bitwise streams is intentional to + * avoid endianness issues. + * * @param bits the amount of data to write (gte 0, lte 8) * @param data to write, will be masked to expose only bits param from lsb */ @@ -95,8 +98,8 @@ public class BitwiseOutputStream { int offset = 16 - (mPos & 0x07) - bits; // &7==%8 data <<= offset; mPos += bits; - mBuf[index] |= (data >>> 8); - if (offset < 8) mBuf[index + 1] |= (data & 0x00FF); + mBuf[index] |= data >>> 8; + if (offset < 8) mBuf[index + 1] |= data & 0xFF; } /** diff --git a/core/jni/Android.mk b/core/jni/Android.mk index b5d3b26..e99971a 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -25,6 +25,7 @@ LOCAL_SRC_FILES:= \ ActivityManager.cpp \ AndroidRuntime.cpp \ CursorWindow.cpp \ + Time.cpp \ com_google_android_gles_jni_EGLImpl.cpp \ com_google_android_gles_jni_GLImpl.cpp.arm \ android_opengl_GLES10.cpp \ @@ -123,7 +124,8 @@ LOCAL_SRC_FILES:= \ com_android_internal_graphics_NativeUtils.cpp \ android_backup_BackupDataInput.cpp \ android_backup_BackupDataOutput.cpp \ - android_backup_FileBackupHelperBase.cpp + android_backup_FileBackupHelperBase.cpp \ + android_backup_BackupHelperDispatcher.cpp LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f8a4df0..c322b17 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -156,6 +156,7 @@ extern int register_android_location_GpsLocationProvider(JNIEnv* env); extern int register_android_backup_BackupDataInput(JNIEnv *env); extern int register_android_backup_BackupDataOutput(JNIEnv *env); extern int register_android_backup_FileBackupHelperBase(JNIEnv *env); +extern int register_android_backup_BackupHelperDispatcher(JNIEnv *env); static AndroidRuntime* gCurRuntime = NULL; @@ -1241,6 +1242,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_backup_BackupDataInput), REG_JNI(register_android_backup_BackupDataOutput), REG_JNI(register_android_backup_FileBackupHelperBase), + REG_JNI(register_android_backup_BackupHelperDispatcher), }; /* diff --git a/libs/ui/Time.cpp b/core/jni/Time.cpp index b553913..f3037f3 100644 --- a/libs/ui/Time.cpp +++ b/core/jni/Time.cpp @@ -1,4 +1,4 @@ -#include <utils/TimeUtils.h> +#include "TimeUtils.h" #include <stdio.h> #include <cutils/tztime.h> diff --git a/include/utils/TimeUtils.h b/core/jni/TimeUtils.h index b19e021..b19e021 100644 --- a/include/utils/TimeUtils.h +++ b/core/jni/TimeUtils.h diff --git a/core/jni/android_backup_BackupHelperDispatcher.cpp b/core/jni/android_backup_BackupHelperDispatcher.cpp new file mode 100644 index 0000000..2e3f0b9 --- /dev/null +++ b/core/jni/android_backup_BackupHelperDispatcher.cpp @@ -0,0 +1,259 @@ +/* + * 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. + */ + +#define LOG_TAG "BackupHelperDispatcher_native" +#include <utils/Log.h> + +#include "JNIHelp.h" +#include <android_runtime/AndroidRuntime.h> + +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> + + +#define VERSION_1_HEADER 0x01706c48 // 'Hlp'1 little endian + +namespace android +{ + +struct chunk_header_v1 { + int headerSize; + int version; + int dataSize; // corresponds to Header.chunkSize + int nameLength; // not including the NULL terminator, which is not written to the file +}; + +// java.io.FileDescriptor +static jfieldID s_descriptorField = 0; +static jfieldID s_chunkSizeField = 0; +static jfieldID s_keyPrefixField = 0; + +static int +readHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj) +{ + chunk_header_v1 flattenedHeader; + int fd; + ssize_t amt; + String8 keyPrefix; + char* buf; + + fd = env->GetIntField(fdObj, s_descriptorField); + + amt = read(fd, &flattenedHeader.headerSize, sizeof(flattenedHeader.headerSize)); + if (amt != sizeof(flattenedHeader.headerSize)) { + return -1; + } + + int remainingHeader = flattenedHeader.headerSize - sizeof(flattenedHeader.headerSize); + + if (flattenedHeader.headerSize < (int)sizeof(chunk_header_v1)) { + LOGW("Skipping unknown header: %d bytes", flattenedHeader.headerSize); + if (remainingHeader > 0) { + lseek(fd, remainingHeader, SEEK_CUR); + // >0 means skip this chunk + return 1; + } + } + + amt = read(fd, &flattenedHeader.version, + sizeof(chunk_header_v1)-sizeof(flattenedHeader.headerSize)); + if (amt <= 0) { + LOGW("Failed reading chunk header"); + return -1; + } + remainingHeader -= sizeof(chunk_header_v1)-sizeof(flattenedHeader.headerSize); + + if (flattenedHeader.version != VERSION_1_HEADER) { + LOGW("Skipping unknown header version: 0x%08x, %d bytes", flattenedHeader.version, + flattenedHeader.headerSize); + if (remainingHeader > 0) { + lseek(fd, remainingHeader, SEEK_CUR); + // >0 means skip this chunk + return 1; + } + } + +#if 0 + LOGD("chunk header:"); + LOGD(" headerSize=%d", flattenedHeader.headerSize); + LOGD(" version=0x%08x", flattenedHeader.version); + LOGD(" dataSize=%d", flattenedHeader.dataSize); + LOGD(" nameLength=%d", flattenedHeader.nameLength); +#endif + + if (flattenedHeader.dataSize < 0 || flattenedHeader.nameLength < 0 || + remainingHeader < flattenedHeader.nameLength) { + LOGW("Malformed V1 header remainingHeader=%d dataSize=%d nameLength=%d", remainingHeader, + flattenedHeader.dataSize, flattenedHeader.nameLength); + return -1; + } + + buf = keyPrefix.lockBuffer(flattenedHeader.nameLength); + if (buf == NULL) { + LOGW("unable to allocate %d bytes", flattenedHeader.nameLength); + return -1; + } + + amt = read(fd, buf, flattenedHeader.nameLength); + buf[flattenedHeader.nameLength] = 0; + + keyPrefix.unlockBuffer(flattenedHeader.nameLength); + + remainingHeader -= flattenedHeader.nameLength; + + if (remainingHeader > 0) { + lseek(fd, remainingHeader, SEEK_CUR); + } + + env->SetIntField(headerObj, s_chunkSizeField, flattenedHeader.dataSize); + env->SetObjectField(headerObj, s_keyPrefixField, env->NewStringUTF(keyPrefix.string())); + + return 0; +} + +static int +skipChunk_native(JNIEnv* env, jobject clazz, jobject fdObj, jint bytesToSkip) +{ + int fd; + + fd = env->GetIntField(fdObj, s_descriptorField); + + lseek(fd, bytesToSkip, SEEK_CUR); + + return 0; +} + +static int +padding_len(int len) +{ + len = len % 4; + return len == 0 ? len : 4 - len; +} + +static int +allocateHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj) +{ + int pos; + jstring nameObj; + int nameLength; + int namePadding; + int headerSize; + int fd; + + fd = env->GetIntField(fdObj, s_descriptorField); + + nameObj = (jstring)env->GetObjectField(headerObj, s_keyPrefixField); + + nameLength = env->GetStringUTFLength(nameObj); + namePadding = padding_len(nameLength); + + headerSize = sizeof(chunk_header_v1) + nameLength + namePadding; + + pos = lseek(fd, 0, SEEK_CUR); + + lseek(fd, headerSize, SEEK_CUR); + + return pos; +} + +static int +writeHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj, jint pos) +{ + int err; + chunk_header_v1 header; + int fd; + int namePadding; + int prevPos; + jstring nameObj; + const char* buf; + + fd = env->GetIntField(fdObj, s_descriptorField); + prevPos = lseek(fd, 0, SEEK_CUR); + + nameObj = (jstring)env->GetObjectField(headerObj, s_keyPrefixField); + header.nameLength = env->GetStringUTFLength(nameObj); + namePadding = padding_len(header.nameLength); + + header.headerSize = sizeof(chunk_header_v1) + header.nameLength + namePadding; + header.version = VERSION_1_HEADER; + header.dataSize = prevPos - (pos + header.headerSize); + + lseek(fd, pos, SEEK_SET); + err = write(fd, &header, sizeof(chunk_header_v1)); + if (err != sizeof(chunk_header_v1)) { + return errno; + } + + buf = env->GetStringUTFChars(nameObj, NULL); + err = write(fd, buf, header.nameLength); + env->ReleaseStringUTFChars(nameObj, buf); + if (err != header.nameLength) { + return errno; + } + + if (namePadding != 0) { + int zero = 0; + err = write(fd, &zero, namePadding); + if (err != namePadding) { + return errno; + } + } + + lseek(fd, prevPos, SEEK_SET); + return 0; +} + +static const JNINativeMethod g_methods[] = { + { "readHeader_native", + "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I", + (void*)readHeader_native }, + { "skipChunk_native", + "(Ljava/io/FileDescriptor;I)I", + (void*)skipChunk_native }, + { "allocateHeader_native", + "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I", + (void*)allocateHeader_native }, + { "writeHeader_native", + "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;I)I", + (void*)writeHeader_native }, +}; + +int register_android_backup_BackupHelperDispatcher(JNIEnv* env) +{ + jclass clazz; + + clazz = env->FindClass("java/io/FileDescriptor"); + LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor"); + s_descriptorField = env->GetFieldID(clazz, "descriptor", "I"); + LOG_FATAL_IF(s_descriptorField == NULL, + "Unable to find descriptor field in java.io.FileDescriptor"); + + clazz = env->FindClass("android/backup/BackupHelperDispatcher$Header"); + LOG_FATAL_IF(clazz == NULL, + "Unable to find class android.backup.BackupHelperDispatcher.Header"); + s_chunkSizeField = env->GetFieldID(clazz, "chunkSize", "I"); + LOG_FATAL_IF(s_chunkSizeField == NULL, + "Unable to find chunkSize field in android.backup.BackupHelperDispatcher.Header"); + s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix", "Ljava/lang/String;"); + LOG_FATAL_IF(s_keyPrefixField == NULL, + "Unable to find keyPrefix field in android.backup.BackupHelperDispatcher.Header"); + + return AndroidRuntime::registerNativeMethods(env, "android/backup/BackupHelperDispatcher", + g_methods, NELEM(g_methods)); +} + +} diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp index 8361212..343fa53 100644 --- a/core/jni/android_bluetooth_common.cpp +++ b/core/jni/android_bluetooth_common.cpp @@ -67,6 +67,11 @@ static Properties adapter_properties[] = { {"Devices", DBUS_TYPE_ARRAY}, }; +typedef union { + char *str_val; + int int_val; + char **array_val; +} property_value; jfieldID get_field(JNIEnv *env, jclass clazz, const char *member, const char *mtype) { @@ -466,258 +471,217 @@ void append_variant(DBusMessageIter *iter, int type, void *val) dbus_message_iter_close_container(iter, &value_iter); } - -//TODO(): Remove code duplication between parse_properties and -//parse_property_change -jobjectArray parse_properties(JNIEnv *env, DBusMessageIter *iter, Properties *properties, - const int max_num_properties) { - DBusMessageIter dict_entry, dict, prop_val, device_val, array_val_iter; - jobjectArray strArray = NULL; - char * property; - char values[max_num_properties][256]; - char **uuid_array = NULL; - char **device_path = NULL; - char **array_elements = NULL; - char *string_val; - uint32_t int_val, bool_val; - int i, j, k, type, array_type, num_array_elements = 0; - int ret, num_properties = 0, num_uuids = 0, num_devices = 0; - - - jclass stringClass = env->FindClass("java/lang/String"); - DBusError err; - dbus_error_init(&err); - - for (i = 0; i < max_num_properties; i++) - memset(values[i], '\0', 128 * sizeof(char)); - - if(dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) - goto failure; - dbus_message_iter_recurse(iter, &dict); - do { - if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY) - goto failure; - dbus_message_iter_recurse(&dict, &dict_entry); - if (dbus_message_iter_get_arg_type(&dict_entry) != DBUS_TYPE_STRING) - goto failure; - dbus_message_iter_get_basic(&dict_entry, &property); - if (!dbus_message_iter_next(&dict_entry)) - goto failure; - if (dbus_message_iter_get_arg_type(&dict_entry) != DBUS_TYPE_VARIANT) - goto failure; - - for (i = 0; i < max_num_properties; i++) { - if (!strncmp(properties[i].name, property, strlen(property))) { - num_properties ++; - break; - } - } - if (i == max_num_properties) - goto failure; - - type = properties[i].type; - dbus_message_iter_recurse(&dict_entry, &prop_val); - if(dbus_message_iter_get_arg_type(&prop_val) != type) { - LOGE("Property type mismatch in parse_properties: %d, expected:%d", - dbus_message_iter_get_arg_type(&prop_val), type); - goto failure; - } - - switch(type) { - case DBUS_TYPE_STRING: - case DBUS_TYPE_OBJECT_PATH: - dbus_message_iter_get_basic(&prop_val, &string_val); - strcpy(values[i], string_val); - break; - case DBUS_TYPE_UINT32: - case DBUS_TYPE_INT16: - dbus_message_iter_get_basic(&prop_val, &int_val); - sprintf(values[i], "%d", int_val); - break; - case DBUS_TYPE_BOOLEAN: - dbus_message_iter_get_basic(&prop_val, &bool_val); - sprintf(values[i], "%s", bool_val ? "true" : "false"); - break; - case DBUS_TYPE_ARRAY: - dbus_message_iter_recurse(&prop_val, &array_val_iter); - array_type = dbus_message_iter_get_arg_type(&array_val_iter); - num_array_elements = 0; - if (array_type == DBUS_TYPE_OBJECT_PATH || - array_type == DBUS_TYPE_STRING){ - do { - num_array_elements++; - } while(dbus_message_iter_next(&array_val_iter)); - dbus_message_iter_recurse(&prop_val, &array_val_iter); - // Allocate an array - array_elements = (char **)malloc(sizeof(char *) * - num_array_elements); - if (!array_elements) - goto failure; - - j = 0; - do { - dbus_message_iter_get_basic(&array_val_iter, &array_elements[j]); - j++; - } while(dbus_message_iter_next(&array_val_iter)); - if (!strncmp(property, "UUIDs", strlen("UUIDs"))) { - num_uuids = num_array_elements; - uuid_array = array_elements; - } else { - num_devices = num_array_elements; - device_path = array_elements; - } - } - break; - default: - goto failure; - } - } while(dbus_message_iter_next(&dict)); - - // Convert it to a array of strings. - strArray = env->NewObjectArray((num_properties + num_array_elements) * 2, - stringClass, NULL); - - j = 0; - for (i = 0; i < max_num_properties; i++) { - if (properties[i].type == DBUS_TYPE_ARRAY) { - if (!strncmp(properties[i].name, "UUIDs", strlen("UUIDs"))) { - num_array_elements = num_uuids; - array_elements = uuid_array; - } else { - num_array_elements = num_devices; - array_elements = device_path; - } - - for (k = 0; k < num_array_elements; k++) { - set_object_array_element(env, strArray, properties[i].name, j++); - set_object_array_element(env, strArray, array_elements[k], j++); - } - } else if (values[i][0] != '\0') { - set_object_array_element(env, strArray, properties[i].name, j++); - set_object_array_element(env, strArray, values[i], j++); - } - } - - if (uuid_array) - free(uuid_array); - if (device_path) - free(device_path); - return strArray; - -failure: - if (dbus_error_is_set(&err)) - LOG_AND_FREE_DBUS_ERROR(&err); - if (uuid_array) - free(uuid_array); - if (device_path) - free(device_path); - return NULL; -} - -jobjectArray create_prop_array(JNIEnv *env, Properties *properties, - int prop_index, void *value, int len ) { - jclass stringClass= env->FindClass("java/lang/String"); - char **prop_val = NULL; - char buf[32] = {'\0'}; - int i, j; - - jobjectArray strArray = env->NewObjectArray(1 + len, stringClass, NULL); - j = 0; - set_object_array_element(env, strArray, properties[prop_index].name, j++); - - if (properties[prop_index].type == DBUS_TYPE_UINT32) { - sprintf(buf, "%d", *(int *) value); - set_object_array_element(env, strArray, buf, j++); - } else if (properties[prop_index].type == DBUS_TYPE_BOOLEAN) { - sprintf(buf, "%s", *(int *) value ? "true" : "false"); - set_object_array_element(env, strArray, buf, j++); - } else if (properties[prop_index].type == DBUS_TYPE_ARRAY) { - prop_val = (char **) value; - for (i = 0; i < len; i++) - set_object_array_element(env, strArray, prop_val[i], j++); - } else { - set_object_array_element(env, strArray, (const char *) value, j++); - } - if (prop_val) - free (prop_val); - return strArray; -} - -jobjectArray parse_property_change(JNIEnv *env, DBusMessage *msg, - Properties *properties, int max_num_properties) { - DBusMessageIter iter, prop_val, array_val_iter; - DBusError err; - void *value; - char *property; +int get_property(DBusMessageIter iter, Properties *properties, + int max_num_properties, int *prop_index, property_value *value, int *len) { + DBusMessageIter prop_val, array_val_iter; + char *property = NULL; uint32_t array_type; - int i, j, type, len, prop_index; + char *str_val; + int i, j, type, int_val; - dbus_error_init(&err); - if (!dbus_message_iter_init(msg, &iter)) - goto failure; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) - goto failure; + return -1; dbus_message_iter_get_basic(&iter, &property); if (!dbus_message_iter_next(&iter)) - goto failure; + return -1; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) - goto failure; + return -1; for (i = 0; i < max_num_properties; i++) { - if (!strncmp(property, properties[i].name, strlen(properties[i].name))) + if (!strncmp(property, properties[i].name, strlen(property))) break; } - prop_index = i; + *prop_index = i; if (i == max_num_properties) - goto failure; + return -1; dbus_message_iter_recurse(&iter, &prop_val); - type = properties[prop_index].type; + type = properties[*prop_index].type; if (dbus_message_iter_get_arg_type(&prop_val) != type) { - LOGE("Property type mismatch in parse_properties: %d, expected:%d", - dbus_message_iter_get_arg_type(&prop_val), type); - goto failure; + LOGE("Property type mismatch in get_property: %d, expected:%d, index:%d", + dbus_message_iter_get_arg_type(&prop_val), type, *prop_index); + return -1; } switch(type) { case DBUS_TYPE_STRING: case DBUS_TYPE_OBJECT_PATH: - dbus_message_iter_get_basic(&prop_val, &value); - len = 1; + dbus_message_iter_get_basic(&prop_val, &value->str_val); + *len = 1; break; case DBUS_TYPE_UINT32: case DBUS_TYPE_INT16: case DBUS_TYPE_BOOLEAN: - uint32_t int_val; dbus_message_iter_get_basic(&prop_val, &int_val); - value = &int_val; - len = 1; + value->int_val = int_val; + *len = 1; break; case DBUS_TYPE_ARRAY: dbus_message_iter_recurse(&prop_val, &array_val_iter); array_type = dbus_message_iter_get_arg_type(&array_val_iter); - len = 0; + *len = 0; + value->array_val = NULL; if (array_type == DBUS_TYPE_OBJECT_PATH || array_type == DBUS_TYPE_STRING){ + j = 0; do { - len ++; + j ++; } while(dbus_message_iter_next(&array_val_iter)); dbus_message_iter_recurse(&prop_val, &array_val_iter); // Allocate an array of char * - char **tmp = (char **)malloc(sizeof(char *) * len); + *len = j; + char **tmp = (char **)malloc(sizeof(char *) * *len); if (!tmp) - goto failure; + return -1; j = 0; do { dbus_message_iter_get_basic(&array_val_iter, &tmp[j]); j ++; } while(dbus_message_iter_next(&array_val_iter)); - value = (char **) tmp; + value->array_val = tmp; } break; default: + return -1; + } + return 0; +} + +void create_prop_array(JNIEnv *env, jobjectArray strArray, Properties *property, + property_value *value, int len, int *array_index ) { + char **prop_val = NULL; + char buf[32] = {'\0'}, buf1[32] = {'\0'}; + int i; + + char *name = property->name; + int prop_type = property->type; + + set_object_array_element(env, strArray, name, *array_index); + *array_index += 1; + + if (prop_type == DBUS_TYPE_UINT32 || prop_type == DBUS_TYPE_INT16) { + sprintf(buf, "%d", value->int_val); + set_object_array_element(env, strArray, buf, *array_index); + *array_index += 1; + } else if (prop_type == DBUS_TYPE_BOOLEAN) { + sprintf(buf, "%s", value->int_val ? "true" : "false"); + + set_object_array_element(env, strArray, buf, *array_index); + *array_index += 1; + } else if (prop_type == DBUS_TYPE_ARRAY) { + // Write the length first + sprintf(buf1, "%d", len); + set_object_array_element(env, strArray, buf1, *array_index); + *array_index += 1; + + prop_val = value->array_val; + for (i = 0; i < len; i++) { + set_object_array_element(env, strArray, prop_val[i], *array_index); + *array_index += 1; + } + } else { + set_object_array_element(env, strArray, (const char *) value->str_val, *array_index); + *array_index += 1; + } +} + +jobjectArray parse_properties(JNIEnv *env, DBusMessageIter *iter, Properties *properties, + const int max_num_properties) { + DBusMessageIter dict_entry, dict; + jobjectArray strArray = NULL; + property_value value; + int i, size = 0,array_index = 0; + int len = 0, prop_type = DBUS_TYPE_INVALID, prop_index = -1, type; + struct { + property_value value; + int len; + bool used; + } values[max_num_properties]; + int t, j; + + jclass stringClass = env->FindClass("java/lang/String"); + DBusError err; + dbus_error_init(&err); + + for (i = 0; i < max_num_properties; i++) { + values[i].used = false; + } + + if(dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + goto failure; + dbus_message_iter_recurse(iter, &dict); + do { + len = 0; + if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY) + goto failure; + dbus_message_iter_recurse(&dict, &dict_entry); + + if (!get_property(dict_entry, properties, max_num_properties, &prop_index, + &value, &len)) { + size += 2; + if (properties[prop_index].type == DBUS_TYPE_ARRAY) + size += len; + values[prop_index].value = value; + values[prop_index].len = len; + values[prop_index].used = true; + } else { + goto failure; + } + } while(dbus_message_iter_next(&dict)); + + strArray = env->NewObjectArray(size, stringClass, NULL); + + for (i = 0; i < max_num_properties; i++) { + if (values[i].used) { + create_prop_array(env, strArray, &properties[i], &values[i].value, values[i].len, + &array_index); + + if (properties[i].type == DBUS_TYPE_ARRAY && values[i].used + && values[i].value.array_val != NULL) + free(values[i].value.array_val); + } + + } + return strArray; + +failure: + if (dbus_error_is_set(&err)) + LOG_AND_FREE_DBUS_ERROR(&err); + for (i = 0; i < max_num_properties; i++) + if (properties[i].type == DBUS_TYPE_ARRAY && values[i].used == true + && values[i].value.array_val != NULL) + free(values[i].value.array_val); + return NULL; +} + +jobjectArray parse_property_change(JNIEnv *env, DBusMessage *msg, + Properties *properties, int max_num_properties) { + DBusMessageIter iter; + DBusError err; + jobjectArray strArray = NULL; + jclass stringClass= env->FindClass("java/lang/String"); + int len = 0, prop_index = -1; + int array_index = 0, size = 0; + property_value value; + + dbus_error_init(&err); + if (!dbus_message_iter_init(msg, &iter)) goto failure; + + if (!get_property(iter, properties, max_num_properties, + &prop_index, &value, &len)) { + size += 2; + if (properties[prop_index].type == DBUS_TYPE_ARRAY) + size += len; + strArray = env->NewObjectArray(size, stringClass, NULL); + + create_prop_array(env, strArray, &properties[prop_index], + &value, len, &array_index); + + if (properties[prop_index].type == DBUS_TYPE_ARRAY && value.array_val != NULL) + free(value.array_val); + + return strArray; } - return create_prop_array(env, properties, prop_index, value, len); failure: LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg); return NULL; diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index c7cc9b3..0420918 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -125,37 +125,8 @@ void JNICameraContext::notify(int32_t msgType, int32_t ext1, int32_t ext2) return; } JNIEnv *env = AndroidRuntime::getJNIEnv(); - - // parse message - switch (msgType) { - case CAMERA_MSG_ERROR: - LOGV("errorCallback"); - int error; - switch (ext1) { - case DEAD_OBJECT: - error = kCameraErrorMediaServer; - break; - default: - error = kCameraErrorUnknown; - break; - } - env->CallStaticVoidMethod(mCameraJClass, fields.post_event, - mCameraJObjectWeak, kErrorCallback, error, 0, NULL); - break; - case CAMERA_MSG_FOCUS: - LOGV("autoFocusCallback"); - env->CallStaticVoidMethod(mCameraJClass, fields.post_event, - mCameraJObjectWeak, kAutoFocusCallback, ext1, 0, NULL); - break; - case CAMERA_MSG_SHUTTER: - LOGV("shutterCallback"); - env->CallStaticVoidMethod(mCameraJClass, fields.post_event, - mCameraJObjectWeak, kShutterCallback, 0, 0, NULL); - break; - default: - LOGV("notifyCallback(%d, %d, %d)", msgType, ext1, ext2); - break; - } + env->CallStaticVoidMethod(mCameraJClass, fields.post_event, + mCameraJObjectWeak, msgType, ext1, ext2); } void JNICameraContext::copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType) @@ -200,30 +171,27 @@ void JNICameraContext::postData(int32_t msgType, const sp<IMemory>& dataPtr) // VM pointer will be NULL if object is released Mutex::Autolock _l(mLock); JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (mCameraJObjectWeak == NULL) { + LOGW("callback on dead camera object"); + return; + } // return data based on callback type switch(msgType) { - case CAMERA_MSG_PREVIEW_FRAME: - LOGV("previewCallback"); - copyAndPost(env, dataPtr, kPreviewCallback); - break; case CAMERA_MSG_VIDEO_FRAME: - LOGV("recordingCallback"); + // should never happen break; + // don't return raw data to Java case CAMERA_MSG_RAW_IMAGE: LOGV("rawCallback"); env->CallStaticVoidMethod(mCameraJClass, fields.post_event, - mCameraJObjectWeak, kRawCallback, 0, 0, NULL); - break; - case CAMERA_MSG_COMPRESSED_IMAGE: - LOGV("jpegCallback"); - copyAndPost(env, dataPtr, kJpegCallback); + mCameraJObjectWeak, msgType, 0, 0, NULL); break; default: LOGV("dataCallback(%d, %p)", msgType, dataPtr.get()); + copyAndPost(env, dataPtr, msgType); break; } - } // connect to camera service @@ -298,7 +266,10 @@ static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz, sp<Camera> camera = get_native_camera(env, thiz, NULL); if (camera == 0) return; - sp<Surface> surface = reinterpret_cast<Surface*>(env->GetIntField(jSurface, fields.surface)); + sp<Surface> surface = NULL; + if (jSurface != NULL) { + surface = reinterpret_cast<Surface*>(env->GetIntField(jSurface, fields.surface)); + } if (camera->setPreviewDisplay(surface) != NO_ERROR) { jniThrowException(env, "java/io/IOException", "setPreviewDisplay failed"); } diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp index 7c208e9..98f4e03 100644 --- a/core/jni/android_text_format_Time.cpp +++ b/core/jni/android_text_format_Time.cpp @@ -23,7 +23,7 @@ #include "jni.h" #include "utils/misc.h" #include "android_runtime/AndroidRuntime.h" -#include <utils/TimeUtils.h> +#include "TimeUtils.h" #include <nativehelper/JNIHelp.h> #include <cutils/tztime.h> diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index d147bcc..2d90ba4 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -535,7 +535,7 @@ static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject c jint keyboard, jint keyboardHidden, jint navigation, jint screenWidth, jint screenHeight, - jint sdkVersion) + jint screenLayout, jint sdkVersion) { AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { @@ -557,6 +557,7 @@ static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject c config.navigation = (uint8_t)navigation; config.screenWidth = (uint16_t)screenWidth; config.screenHeight = (uint16_t)screenHeight; + config.screenLayout = (uint8_t)screenLayout; config.sdkVersion = (uint16_t)sdkVersion; config.minorVersion = 0; am->setConfiguration(config, locale8); @@ -1567,7 +1568,7 @@ static JNINativeMethod gAssetManagerMethods[] = { (void*) android_content_AssetManager_setLocale }, { "getLocales", "()[Ljava/lang/String;", (void*) android_content_AssetManager_getLocales }, - { "setConfiguration", "(IILjava/lang/String;IIIIIIIII)V", + { "setConfiguration", "(IILjava/lang/String;IIIIIIIIII)V", (void*) android_content_AssetManager_setConfiguration }, { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", (void*) android_content_AssetManager_getResourceIdentifier }, diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 1c9ee7d..770c755 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -269,9 +269,9 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz, jint pid, jint pri) { - if (pri == ANDROID_PRIORITY_BACKGROUND) { + if (pri >= ANDROID_PRIORITY_BACKGROUND) { add_pid_to_cgroup(pid, ANDROID_TGROUP_BG_NONINTERACT); - } else if (getpriority(PRIO_PROCESS, pid) == ANDROID_PRIORITY_BACKGROUND) { + } else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) { add_pid_to_cgroup(pid, ANDROID_TGROUP_DEFAULT); } @@ -496,7 +496,7 @@ void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileSt const String8& field = fields[i]; if (strncmp(p, field.string(), field.length()) == 0) { p += field.length(); - while (*p == ' ') p++; + while (*p == ' ' || *p == '\t') p++; char* num = p; while (*p >= '0' && *p <= '9') p++; skipToEol = *p != '\n'; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0c90769..31b6519 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -973,7 +973,7 @@ <permission android:name="android.permission.BACKUP" android:label="@string/permlab_backup" android:description="@string/permdesc_backup" - android:protectionLevel="signature" /> + android:protectionLevel="signatureOrSystem" /> <!-- Allows an application to tell the AppWidget service which application can access AppWidget's data. The normal user flow is that a user @@ -1000,6 +1000,7 @@ android:hasCode="false" android:label="@string/android_system_label" android:allowClearUserData="false" + android:backupAgent="com.android.internal.backup.SystemBackupAgent" android:icon="@drawable/ic_launcher_android"> <activity android:name="com.android.internal.app.ChooserActivity" android:theme="@style/Theme.Dialog.Alert" diff --git a/core/res/res/drawable/progress.xml b/core/res/res/drawable/progress.xml deleted file mode 100644 index d270520..0000000 --- a/core/res/res/drawable/progress.xml +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/res/drawable/progress.xml -** -** 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. -*/ ---> - -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:drawable="@android:drawable/progress_circular_background" /> - <item> - <shape android:shape="ring" - android:innerRadiusRatio="3.4" - android:thicknessRatio="6.0"> - <gradient - android:useLevel="true" - android:type="sweep" - android:startColor="#ff000000" - android:endColor="#ffffffff" /> - </shape> - </item> - <item> - <rotate - android:pivotX="50%" android:pivotY="50%" - android:fromDegrees="0" android:toDegrees="360" - android:drawable="@android:drawable/progress_particle" /> - </item> -</layer-list> diff --git a/core/res/res/drawable/progress_circular_background.png b/core/res/res/drawable/progress_circular_background.png Binary files differdeleted file mode 100644 index 7c637fd..0000000 --- a/core/res/res/drawable/progress_circular_background.png +++ /dev/null diff --git a/core/res/res/drawable/progress_circular_background_small.png b/core/res/res/drawable/progress_circular_background_small.png Binary files differdeleted file mode 100644 index 6b8ba9b..0000000 --- a/core/res/res/drawable/progress_circular_background_small.png +++ /dev/null diff --git a/core/res/res/drawable/progress_circular_indeterminate.png b/core/res/res/drawable/progress_circular_indeterminate.png Binary files differdeleted file mode 100644 index 125a264..0000000 --- a/core/res/res/drawable/progress_circular_indeterminate.png +++ /dev/null diff --git a/core/res/res/drawable/progress_large.xml b/core/res/res/drawable/progress_large.xml index 4669104..4f016bc 100644 --- a/core/res/res/drawable/progress_large.xml +++ b/core/res/res/drawable/progress_large.xml @@ -1,45 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- 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. +<!-- +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ --> - - -<rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:pivotX="50%" android:pivotY="50%" - android:fromDegrees="0" android:toDegrees="360"> - - <shape - android:shape="ring" - android:innerRadiusRatio="3" - android:thicknessRatio="8" - android:useLevel="false"> - - <size - android:width="76dip" - android:height="76dip" - /> - - <gradient - android:type="sweep" - android:useLevel="false" - android:startColor="#4c737373" - android:centerColor="#4c737373" - android:centerY="0.50" - android:endColor="#ffffd300" - /> - - </shape> - -</rotate> - +<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/spinner_black_76" + android:pivotX="50%" + android:pivotY="50%" + android:framesCount="12" + android:frameDuration="100" /> diff --git a/core/res/res/drawable/progress_indeterminate.xml b/core/res/res/drawable/progress_large_white.xml index 1bf715e..c690ed4 100644 --- a/core/res/res/drawable/progress_indeterminate.xml +++ b/core/res/res/drawable/progress_large_white.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* //device/apps/common/res/drawable/progress.xml +/* ** -** Copyright 2007, The Android Open Source Project +** Copyright 2009, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -17,16 +17,9 @@ ** limitations under the License. */ --> - -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:drawable="@android:drawable/progress_circular_background" /> - - <item><rotate - android:pivotX="50%" - android:pivotY="50%" - android:fromDegrees="0" - android:toDegrees="360" - android:drawable="@android:drawable/progress_circular_indeterminate" /> - </item> -</layer-list> +<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/spinner_white_76" + android:pivotX="50%" + android:pivotY="50%" + android:framesCount="12" + android:frameDuration="100" /> diff --git a/core/res/res/drawable/progress_medium.xml b/core/res/res/drawable/progress_medium.xml index 92aebb5..eb1bd50 100644 --- a/core/res/res/drawable/progress_medium.xml +++ b/core/res/res/drawable/progress_medium.xml @@ -1,43 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- 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. +<!-- +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ --> - -<rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:pivotX="50%" android:pivotY="50%" - android:fromDegrees="0" android:toDegrees="360"> - - <shape - android:shape="ring" - android:innerRadiusRatio="3" - android:thicknessRatio="8" - android:useLevel="false"> - - <size - android:width="48dip" - android:height="48dip" - /> - - <gradient - android:type="sweep" - android:useLevel="false" - android:startColor="#4c737373" - android:centerColor="#4c737373" - android:centerY="0.50" - android:endColor="#ffffd300" - /> - - </shape> - -</rotate> +<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/spinner_black_48" + android:pivotX="50%" + android:pivotY="50%" + android:framesCount="12" + android:frameDuration="100" /> diff --git a/core/res/res/drawable/progress_medium_white.xml b/core/res/res/drawable/progress_medium_white.xml new file mode 100644 index 0000000..b4f9b31 --- /dev/null +++ b/core/res/res/drawable/progress_medium_white.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/spinner_white_48" + android:pivotX="50%" + android:pivotY="50%" + android:framesCount="12" + android:frameDuration="100" /> diff --git a/core/res/res/drawable/progress_small.xml b/core/res/res/drawable/progress_small.xml index e5b0021..e0ee5e4 100644 --- a/core/res/res/drawable/progress_small.xml +++ b/core/res/res/drawable/progress_small.xml @@ -1,45 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- 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. +<!-- +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ --> - - -<rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:pivotX="50%" android:pivotY="50%" - android:fromDegrees="0" android:toDegrees="360"> - - <!-- An extra pixel is added on both ratios for stroke --> - <shape - android:shape="ring" - android:innerRadiusRatio="3.2" - android:thicknessRatio="5.333" - android:useLevel="false"> - - <size - android:width="16dip" - android:height="16dip" - /> - - <gradient - android:type="sweep" - android:useLevel="false" - android:startColor="#4c737373" - android:centerColor="#4c737373" - android:centerY="0.50" - android:endColor="#ffffd300" - /> - - </shape> - -</rotate> +<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/spinner_black_16" + android:pivotX="50%" + android:pivotY="50%" + android:framesCount="12" + android:frameDuration="100" /> diff --git a/core/res/res/drawable/progress_small_titlebar.xml b/core/res/res/drawable/progress_small_titlebar.xml index cf8e41c..8cfba86 100644 --- a/core/res/res/drawable/progress_small_titlebar.xml +++ b/core/res/res/drawable/progress_small_titlebar.xml @@ -1,45 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- 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. +<!-- +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ --> - - -<rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:pivotX="50%" android:pivotY="50%" - android:fromDegrees="0" android:toDegrees="360"> - - <!-- An extra pixel is added on both ratios for stroke --> - <shape - android:shape="ring" - android:innerRadiusRatio="3.2" - android:thicknessRatio="5.333" - android:useLevel="false"> - - <size - android:width="16dip" - android:height="16dip" - /> - - <gradient - android:type="sweep" - android:useLevel="false" - android:startColor="#ff666666" - android:centerColor="#ff666666" - android:centerY="0.50" - android:endColor="#ffffd300" - /> - - </shape> - -</rotate> +<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/spinner_white_16" + android:pivotX="50%" + android:pivotY="50%" + android:framesCount="12" + android:frameDuration="100" /> diff --git a/core/res/res/drawable/progress_small_white.xml b/core/res/res/drawable/progress_small_white.xml new file mode 100644 index 0000000..8cfba86 --- /dev/null +++ b/core/res/res/drawable/progress_small_white.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/spinner_white_16" + android:pivotX="50%" + android:pivotY="50%" + android:framesCount="12" + android:frameDuration="100" /> diff --git a/core/res/res/drawable/search_spinner.xml b/core/res/res/drawable/search_spinner.xml index 34c163d..31a77c3 100644 --- a/core/res/res/drawable/search_spinner.xml +++ b/core/res/res/drawable/search_spinner.xml @@ -2,7 +2,7 @@ <!-- /* ** -** Copyright 2008, The Android Open Source Project +** Copyright 2009, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -17,20 +17,9 @@ ** limitations under the License. */ --> -<animation-list - xmlns:android="http://schemas.android.com/apk/res/android" - android:oneshot="false"> - <item android:drawable="@drawable/search_spinner_anim1" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim2" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim3" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim4" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim5" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim6" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim7" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim8" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim9" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim10" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim11" android:duration="150" /> - <item android:drawable="@drawable/search_spinner_anim12" android:duration="150" /> -</animation-list> - +<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/spinner_black_20" + android:pivotX="50%" + android:pivotY="50%" + android:framesCount="12" + android:frameDuration="100" /> diff --git a/core/res/res/drawable/search_spinner_anim10.png b/core/res/res/drawable/search_spinner_anim10.png Binary files differdeleted file mode 100755 index 9611d97..0000000 --- a/core/res/res/drawable/search_spinner_anim10.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim11.png b/core/res/res/drawable/search_spinner_anim11.png Binary files differdeleted file mode 100755 index 4261704..0000000 --- a/core/res/res/drawable/search_spinner_anim11.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim12.png b/core/res/res/drawable/search_spinner_anim12.png Binary files differdeleted file mode 100755 index 0602314..0000000 --- a/core/res/res/drawable/search_spinner_anim12.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim2.png b/core/res/res/drawable/search_spinner_anim2.png Binary files differdeleted file mode 100755 index 05d58e0..0000000 --- a/core/res/res/drawable/search_spinner_anim2.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim3.png b/core/res/res/drawable/search_spinner_anim3.png Binary files differdeleted file mode 100755 index 69fa9c1..0000000 --- a/core/res/res/drawable/search_spinner_anim3.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim4.png b/core/res/res/drawable/search_spinner_anim4.png Binary files differdeleted file mode 100755 index 9201bac..0000000 --- a/core/res/res/drawable/search_spinner_anim4.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim5.png b/core/res/res/drawable/search_spinner_anim5.png Binary files differdeleted file mode 100755 index f0c7101..0000000 --- a/core/res/res/drawable/search_spinner_anim5.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim6.png b/core/res/res/drawable/search_spinner_anim6.png Binary files differdeleted file mode 100755 index 99d1d4e..0000000 --- a/core/res/res/drawable/search_spinner_anim6.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim7.png b/core/res/res/drawable/search_spinner_anim7.png Binary files differdeleted file mode 100755 index 8ca3358..0000000 --- a/core/res/res/drawable/search_spinner_anim7.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim8.png b/core/res/res/drawable/search_spinner_anim8.png Binary files differdeleted file mode 100755 index 408d723..0000000 --- a/core/res/res/drawable/search_spinner_anim8.png +++ /dev/null diff --git a/core/res/res/drawable/search_spinner_anim9.png b/core/res/res/drawable/search_spinner_anim9.png Binary files differdeleted file mode 100755 index 42a2c65..0000000 --- a/core/res/res/drawable/search_spinner_anim9.png +++ /dev/null diff --git a/core/res/res/drawable/spinner_black_16.png b/core/res/res/drawable/spinner_black_16.png Binary files differnew file mode 100644 index 0000000..5ee33ce --- /dev/null +++ b/core/res/res/drawable/spinner_black_16.png diff --git a/core/res/res/drawable/search_spinner_anim1.png b/core/res/res/drawable/spinner_black_20.png Binary files differindex e55b60d..e55b60d 100755 --- a/core/res/res/drawable/search_spinner_anim1.png +++ b/core/res/res/drawable/spinner_black_20.png diff --git a/core/res/res/drawable/spinner_black_48.png b/core/res/res/drawable/spinner_black_48.png Binary files differnew file mode 100644 index 0000000..3a68192 --- /dev/null +++ b/core/res/res/drawable/spinner_black_48.png diff --git a/core/res/res/drawable/spinner_black_76.png b/core/res/res/drawable/spinner_black_76.png Binary files differnew file mode 100644 index 0000000..ec57460 --- /dev/null +++ b/core/res/res/drawable/spinner_black_76.png diff --git a/core/res/res/drawable/progress_particle.png b/core/res/res/drawable/spinner_white_16.png Binary files differindex 9160108..dd2e1fd 100644 --- a/core/res/res/drawable/progress_particle.png +++ b/core/res/res/drawable/spinner_white_16.png diff --git a/core/res/res/drawable/spinner_white_48.png b/core/res/res/drawable/spinner_white_48.png Binary files differnew file mode 100644 index 0000000..d25a33e --- /dev/null +++ b/core/res/res/drawable/spinner_white_48.png diff --git a/core/res/res/drawable/spinner_white_76.png b/core/res/res/drawable/spinner_white_76.png Binary files differnew file mode 100644 index 0000000..f53e8ff --- /dev/null +++ b/core/res/res/drawable/spinner_white_76.png diff --git a/core/res/res/layout/character_picker.xml b/core/res/res/layout/character_picker.xml index bb4955a..0344849 100644 --- a/core/res/res/layout/character_picker.xml +++ b/core/res/res/layout/character_picker.xml @@ -23,8 +23,8 @@ android:id="@+id/characterPicker" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:padding="12dp" - android:verticalSpacing="8dp" + android:padding="4dp" + android:verticalSpacing="4dp" android:horizontalSpacing="8dp" android:stretchMode="spacingWidth" android:gravity="left" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 78567fc..4c84732 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -350,6 +350,12 @@ <attr name="progressBarStyleSmallTitle" format="reference" /> <!-- Large ProgressBar style. This is a large circular progress bar. --> <attr name="progressBarStyleLarge" format="reference" /> + <!-- Inverse ProgressBar style. This is a medium circular progress bar. --> + <attr name="progressBarStyleInverse" format="reference" /> + <!-- Small inverse ProgressBar style. This is a small circular progress bar. --> + <attr name="progressBarStyleSmallInverse" format="reference" /> + <!-- Large inverse ProgressBar style. This is a large circular progress bar. --> + <attr name="progressBarStyleLargeInverse" format="reference" /> <!-- Default SeekBar style. --> <attr name="seekBarStyle" format="reference" /> <!-- Default RatingBar style. --> @@ -2327,6 +2333,15 @@ <attr name="drawable" /> </declare-styleable> + <declare-styleable name="AnimatedRotateDrawable"> + <attr name="visible" /> + <attr name="frameDuration" format="integer" /> + <attr name="framesCount" format="integer" /> + <attr name="pivotX" /> + <attr name="pivotY" /> + <attr name="drawable" /> + </declare-styleable> + <declare-styleable name="InsetDrawable"> <attr name="visible" /> <attr name="drawable" /> @@ -3326,5 +3341,18 @@ <attr name="accountType"/> </declare-styleable> + <!-- =============================== --> + <!-- Contacts meta-data attributes --> + <!-- =============================== --> + + <declare-styleable name="Icon"> + <attr name="icon" /> + <attr name="mimeType" /> + </declare-styleable> + + <declare-styleable name="IconDefault"> + <attr name="icon" /> + </declare-styleable> + </resources> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 91cd9fd..7571e24 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -512,6 +512,9 @@ <!-- The screen orientation has changed, that is the user has rotated the device. --> <flag name="orientation" value="0x0080" /> + <!-- The screen orientation has changed, that is the user has + rotated the device. --> + <flag name="screenLayout" value="0x0100" /> <!-- The font scaling factor has changed, that is the user has selected a new global font size. --> <flag name="fontScale" value="0x40000000" /> @@ -829,8 +832,59 @@ <p>This appears as a child tag of the {@link #AndroidManifest manifest} tag. --> <declare-styleable name="AndroidManifestSupportsDensity" parent="AndroidManifest"> - <!-- Required value of the density in dip (device independent pixel). --> - <attr name="density" format="integer" /> + <!-- Required value of the density in dip (device independent pixel). + You should use one of the pre-defined constants for the standard + screen densities defined here. + --> + <attr name="density" format="integer"> + <!-- A low density screen, such as a QVGA or WQVGA screen in a + typical hand-held phone. The constant for this is 120. --> + <enum name="low" value="120" /> + <!-- A medium density screen, such as an HVGA screen in a + typical hand-held phone. The constant for this is 160. --> + <enum name="medium" value="160" /> + <!-- A high density screen, such as a VGA or WVGA screen in a + typical hand-held phone. The constant for this is 240. --> + <enum name="high" value="240" /> + </attr> + </declare-styleable> + + <!-- The <code>supports-screens</code> specifies the screen dimensions an + application supports. By default a modern application supports all + screen sizes and must explicitly disable certain screen sizes here; + older applications are assumed to only support the traditional normal + (HVGA) screen size. Note that screen size is a separate axis from + density, and is determined as the available pixels to an application + after density scaling has been applied. + + <p>This appears as a child tag of the + {@link #AndroidManifest manifest} tag. --> + <declare-styleable name="AndroidManifestSupportsScreens" parent="AndroidManifest"> + <!-- Indicates whether the application supports smaller screen form-factors. + A small screen is defined as one with a smaller aspect ratio than + the traditional HVGA screen; that is, for a portrait screen, less + tall than an HVGA screen. In practice, this means a QVGA low + density or VGA high density screen. An application that does + not support small screens <em>will not be available</em> for + small screen devices, since there is little the platform can do + to make such an application work on a smaller screen. --> + <attr name="smallScreens" format="boolean" /> + <!-- Indicates whether an application supports the normal screen + form-factors. Traditionally this is an HVGA normal density + screen, but WQVGA low density and WVGA high density are also + considered to be normal. This attribute is true by default, + and applications currently should leave it that way. --> + <attr name="normalScreens" format="boolean" /> + <!-- Indicates whether the application supports larger screen form-factors. + A large screen is defined as a screen that is significantly larger + than a normal phone screen, and thus may require some special care + on the application's part to make good use of it. An example would + be a VGA <em>normal density</em> screen, though even larger screens + are certainly possible. An application that does not support + large screens will be placed as a postage stamp on such a + screen, so that it retains the dimensions it was originally + designed for. --> + <attr name="largeScreens" format="boolean" /> </declare-styleable> <!-- The <code>expandable</code> specifies if this package supports screen metrics diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 079baf7..414382d 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1117,11 +1117,20 @@ <public type="attr" name="glEsVersion" /> <public type="attr" name="queryAfterZeroResults" /> <public type="attr" name="dropDownHeight" /> + <public type="attr" name="smallScreens" /> + <public type="attr" name="normalScreens" /> + <public type="attr" name="largeScreens" /> + <public type="attr" name="progressBarStyleInverse" /> + <public type="attr" name="progressBarStyleSmallInverse" /> + <public type="attr" name="progressBarStyleLargeInverse" /> <public-padding type="attr" name="donut_resource_pad" end="0x0101029f" /> <public-padding type="id" name="donut_resource_pad" end="0x01020040" /> + <public type="style" name="Widget.ProgressBar.Inverse" id="0x0103005b" /> + <public type="style" name="Widget.ProgressBar.Large.Inverse" id="0x0103005c" /> + <public type="style" name="Widget.ProgressBar.Small.Inverse" id="0x0103005d" /> <public-padding type="style" name="donut_resource_pad" end="0x01030070" /> <public-padding type="string" name="donut_resource_pad" end="0x01040030" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 88464f7..612e6f4 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1325,7 +1325,7 @@ <!-- Do not translate. WebView User Agent string --> <string name="web_user_agent"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android %s) - AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1</xliff:g></string> + AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17</xliff:g></string> <!-- Title for a JavaScript dialog. "The page at <url of current page> says:" --> <string name="js_dialog_title">The page at \'<xliff:g id="title">%s</xliff:g>\' says:</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 648a7dd..7d235ec 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -243,7 +243,7 @@ <style name="Widget.ProgressBar"> <item name="android:indeterminateOnly">true</item> - <item name="android:indeterminateDrawable">@android:drawable/progress_medium</item> + <item name="android:indeterminateDrawable">@android:drawable/progress_medium_white</item> <item name="android:indeterminateBehavior">repeat</item> <item name="android:indeterminateDuration">3500</item> <item name="android:minWidth">48dip</item> @@ -253,7 +253,7 @@ </style> <style name="Widget.ProgressBar.Large"> - <item name="android:indeterminateDrawable">@android:drawable/progress_large</item> + <item name="android:indeterminateDrawable">@android:drawable/progress_large_white</item> <item name="android:minWidth">76dip</item> <item name="android:maxWidth">76dip</item> <item name="android:minHeight">76dip</item> @@ -261,13 +261,25 @@ </style> <style name="Widget.ProgressBar.Small"> - <item name="android:indeterminateDrawable">@android:drawable/progress_small</item> + <item name="android:indeterminateDrawable">@android:drawable/progress_small_white</item> <item name="android:minWidth">16dip</item> <item name="android:maxWidth">16dip</item> <item name="android:minHeight">16dip</item> <item name="android:maxHeight">16dip</item> </style> + <style name="Widget.ProgressBar.Inverse"> + <item name="android:indeterminateDrawable">@android:drawable/progress_medium</item> + </style> + + <style name="Widget.ProgressBar.Large.Inverse"> + <item name="android:indeterminateDrawable">@android:drawable/progress_large</item> + </style> + + <style name="Widget.ProgressBar.Small.Inverse"> + <item name="android:indeterminateDrawable">@android:drawable/progress_small</item> + </style> + <style name="Widget.ProgressBar.Small.Title"> <item name="android:indeterminateDrawable">@android:drawable/progress_small_titlebar</item> </style> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index f37d514..bd6e1df 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -154,6 +154,9 @@ <item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small</item> <item name="progressBarStyleSmallTitle">@android:style/Widget.ProgressBar.Small.Title</item> <item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large</item> + <item name="progressBarStyleInverse">@android:style/Widget.ProgressBar.Inverse</item> + <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small.Inverse</item> + <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large.Inverse</item> <item name="seekBarStyle">@android:style/Widget.SeekBar</item> <item name="ratingBarStyle">@android:style/Widget.RatingBar</item> <item name="ratingBarStyleIndicator">@android:style/Widget.RatingBar.Indicator</item> @@ -233,6 +236,13 @@ <item name="listViewStyle">@android:style/Widget.ListView.White</item> <item name="listDivider">@drawable/divider_horizontal_bright</item> <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator.White</item> + + <item name="progressBarStyle">@android:style/Widget.ProgressBar.Inverse</item> + <item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small.Inverse</item> + <item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large.Inverse</item> + <item name="progressBarStyleInverse">@android:style/Widget.ProgressBar</item> + <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small</item> + <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large</item> </style> <!-- Variant of the light theme with no title bar --> diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index 2b24ef2..778c903 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -57,7 +57,9 @@ public class NinePatch { mBitmap = patch.mBitmap; mChunk = patch.mChunk; mSrcName = patch.mSrcName; - mPaint = new Paint(patch.mPaint); + if (patch.mPaint != null) { + mPaint = new Paint(patch.mPaint); + } validateNinePatchChunk(mBitmap.ni(), mChunk); } @@ -120,7 +122,6 @@ public class NinePatch { public native static boolean isNinePatchChunk(byte[] chunk); - private final Rect mRect = new Rect(); private final Bitmap mBitmap; private final byte[] mChunk; private Paint mPaint; diff --git a/graphics/java/android/graphics/drawable/Animatable.java b/graphics/java/android/graphics/drawable/Animatable.java new file mode 100644 index 0000000..9dc62c3 --- /dev/null +++ b/graphics/java/android/graphics/drawable/Animatable.java @@ -0,0 +1,39 @@ +/* + * 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.graphics.drawable; + +/** + * Interface that drawables suporting animations should implement. + */ +public interface Animatable { + /** + * Starts the drawable's animation. + */ + void start(); + + /** + * Stops the drawable's animation. + */ + void stop(); + + /** + * Indicates whether the animation is running. + * + * @return True if the animation is running, false otherwise. + */ + boolean isRunning(); +} diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java new file mode 100644 index 0000000..ac96f20 --- /dev/null +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -0,0 +1,332 @@ +/* + * 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.graphics.drawable; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.ColorFilter; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.util.Log; +import android.os.SystemClock; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +import com.android.internal.R; + +/** + * @hide + */ +public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable, + Animatable { + + private AnimatedRotateState mState; + private boolean mMutated; + private float mCurrentDegrees; + private float mIncrement; + private boolean mRunning; + + public AnimatedRotateDrawable() { + this(null); + } + + private AnimatedRotateDrawable(AnimatedRotateState rotateState) { + mState = new AnimatedRotateState(rotateState, this); + init(); + } + + private void init() { + final AnimatedRotateState state = mState; + mIncrement = 360.0f / (float) state.mFramesCount; + final Drawable drawable = state.mDrawable; + if (drawable != null) { + drawable.setFilterBitmap(true); + if (drawable instanceof BitmapDrawable) { + ((BitmapDrawable) drawable).setAntiAlias(true); + } + } + } + + public void draw(Canvas canvas) { + int saveCount = canvas.save(); + + final AnimatedRotateState st = mState; + final Drawable drawable = st.mDrawable; + final Rect bounds = drawable.getBounds(); + + int w = bounds.right - bounds.left; + int h = bounds.bottom - bounds.top; + + float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; + float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; + + canvas.rotate(mCurrentDegrees, px, py); + + drawable.draw(canvas); + + canvas.restoreToCount(saveCount); + } + + public void start() { + if (!mRunning) { + mRunning = true; + nextFrame(); + } + } + + public void stop() { + mRunning = false; + unscheduleSelf(this); + } + + public boolean isRunning() { + return mRunning; + } + + private void nextFrame() { + unscheduleSelf(this); + scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration); + } + + public void run() { + // TODO: This should be computed in draw(Canvas), based on the amount + // of time since the last frame drawn + mCurrentDegrees += mIncrement; + if (mCurrentDegrees > (360.0f - mIncrement)) { + mCurrentDegrees = 0.0f; + } + invalidateSelf(); + nextFrame(); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + mState.mDrawable.setVisible(visible, restart); + boolean changed = super.setVisible(visible, restart); + if (visible) { + if (changed || restart) { + mCurrentDegrees = 0.0f; + nextFrame(); + } + } else { + unscheduleSelf(this); + } + return changed; + } + + /** + * Returns the drawable rotated by this RotateDrawable. + */ + public Drawable getDrawable() { + return mState.mDrawable; + } + + @Override + public int getChangingConfigurations() { + return super.getChangingConfigurations() + | mState.mChangingConfigurations + | mState.mDrawable.getChangingConfigurations(); + } + + public void setAlpha(int alpha) { + mState.mDrawable.setAlpha(alpha); + } + + public void setColorFilter(ColorFilter cf) { + mState.mDrawable.setColorFilter(cf); + } + + public int getOpacity() { + return mState.mDrawable.getOpacity(); + } + + public void invalidateDrawable(Drawable who) { + if (mCallback != null) { + mCallback.invalidateDrawable(this); + } + } + + public void scheduleDrawable(Drawable who, Runnable what, long when) { + if (mCallback != null) { + mCallback.scheduleDrawable(this, what, when); + } + } + + public void unscheduleDrawable(Drawable who, Runnable what) { + if (mCallback != null) { + mCallback.unscheduleDrawable(this, what); + } + } + + @Override + public boolean getPadding(Rect padding) { + return mState.mDrawable.getPadding(padding); + } + + @Override + public boolean isStateful() { + return mState.mDrawable.isStateful(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + mState.mDrawable.setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + @Override + public int getIntrinsicWidth() { + return mState.mDrawable.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mState.mDrawable.getIntrinsicHeight(); + } + + @Override + public ConstantState getConstantState() { + if (mState.canConstantState()) { + mState.mChangingConfigurations = super.getChangingConfigurations(); + return mState; + } + return null; + } + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException { + + final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedRotateDrawable); + + super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); + + TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); + final boolean pivotXRel = tv.type == TypedValue.TYPE_FRACTION; + final float pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + + tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); + final boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION; + final float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + + final int framesCount = a.getInt(R.styleable.AnimatedRotateDrawable_framesCount, 12); + final int frameDuration = a.getInt(R.styleable.AnimatedRotateDrawable_frameDuration, 150); + + final int res = a.getResourceId(R.styleable.AnimatedRotateDrawable_drawable, 0); + Drawable drawable = null; + if (res > 0) { + drawable = r.getDrawable(res); + } + + a.recycle(); + + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && + (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) { + Log.w("drawable", "Bad element under <animated-rotate>: " + + parser .getName()); + } + } + + if (drawable == null) { + Log.w("drawable", "No drawable specified for <animated-rotate>"); + } + + final AnimatedRotateState rotateState = mState; + rotateState.mDrawable = drawable; + rotateState.mPivotXRel = pivotXRel; + rotateState.mPivotX = pivotX; + rotateState.mPivotYRel = pivotYRel; + rotateState.mPivotY = pivotY; + rotateState.mFramesCount = framesCount; + rotateState.mFrameDuration = frameDuration; + + init(); + + if (drawable != null) { + drawable.setCallback(this); + } + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mState.mDrawable.mutate(); + mMutated = true; + } + return this; + } + + final static class AnimatedRotateState extends Drawable.ConstantState { + Drawable mDrawable; + + int mChangingConfigurations; + + boolean mPivotXRel; + float mPivotX; + boolean mPivotYRel; + float mPivotY; + int mFrameDuration; + int mFramesCount; + + private boolean mCanConstantState; + private boolean mCheckedConstantState; + + public AnimatedRotateState(AnimatedRotateState source, AnimatedRotateDrawable owner) { + if (source != null) { + mDrawable = source.mDrawable.getConstantState().newDrawable(); + mDrawable.setCallback(owner); + mPivotXRel = source.mPivotXRel; + mPivotX = source.mPivotX; + mPivotYRel = source.mPivotYRel; + mPivotY = source.mPivotY; + mFramesCount = source.mFramesCount; + mFrameDuration = source.mFrameDuration; + mCanConstantState = mCheckedConstantState = true; + } + } + + @Override + public Drawable newDrawable() { + return new AnimatedRotateDrawable(this); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + + public boolean canConstantState() { + if (!mCheckedConstantState) { + mCanConstantState = mDrawable.getConstantState() != null; + mCheckedConstantState = true; + } + + return mCanConstantState; + } + } +} diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index bab1703..68718c9 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -71,7 +71,7 @@ import android.util.AttributeSet; * @attr ref android.R.styleable#AnimationDrawableItem_duration * @attr ref android.R.styleable#AnimationDrawableItem_drawable */ -public class AnimationDrawable extends DrawableContainer implements Runnable { +public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable { private final AnimationState mAnimationState; private int mCurFrame = -1; private boolean mMutated; diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index f0d49f5..910e111 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -745,6 +745,8 @@ public abstract class Drawable { drawable = new ClipDrawable(); } else if (name.equals("rotate")) { drawable = new RotateDrawable(); + } else if (name.equals("animated-rotate")) { + drawable = new AnimatedRotateDrawable(); } else if (name.equals("animation-list")) { drawable = new AnimationDrawable(); } else if (name.equals("inset")) { diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index f8b88d0..376b1df 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -234,8 +234,10 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - for (Drawable child : mDrawableContainerState.mDrawables) { - child.mutate(); + final int N = mDrawableContainerState.getChildCount(); + final Drawable[] drawables = mDrawableContainerState.getChildren(); + for (int i = 0; i < N; i++) { + drawables[i].mutate(); } mMutated = true; } diff --git a/include/tts/TtsEngine.h b/include/tts/TtsEngine.h index 8486532..21cb73b 100644 --- a/include/tts/TtsEngine.h +++ b/include/tts/TtsEngine.h @@ -69,6 +69,14 @@ enum tts_result { TTS_MISSING_RESOURCES = -6 }; +enum tts_support_result { + TTS_LANG_COUNTRY_VAR_AVAILABLE = 2, + TTS_LANG_COUNTRY_AVAILABLE = 1, + TTS_LANG_AVAILABLE = 0, + TTS_LANG_MISSING_DATA = -1, + TTS_LANG_NOT_SUPPORTED = -2 +}; + class TtsEngine { public: @@ -86,19 +94,32 @@ public: // @return TTS_SUCCESS, or TTS_FAILURE virtual tts_result stop(); + // Returns the level of support for the language, country and variant. + // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported, + // and the corresponding resources are correctly installed + // TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the + // corresponding resources are correctly installed, but there is no match for + // the specified variant + // TTS_LANG_AVAILABLE if the language is supported and the + // corresponding resources are correctly installed, but there is no match for + // the specified country and variant + // TTS_LANG_MISSING_DATA if the required resources to provide any level of support + // for the language are not correctly installed + // TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine. + virtual tts_support_result isLanguageAvailable(const char *lang, const char *country, + const char *variant); + // Load the resources associated with the specified language. The loaded // language will only be used once a call to setLanguage() with the same - // language value is issued. Language values are based on the Android - // conventions for localization as described in the Android platform - // documentation on internationalization. This implies that language - // data is specified in the format xx-rYY, where xx is a two letter - // ISO 639-1 language code in lowercase and rYY is a two letter - // ISO 3166-1-alpha-2 language code in uppercase preceded by a - // lowercase "r". - // @param value pointer to the language value - // @param size length of the language value + // language value is issued. Language and country values are coded according to the ISO three + // letter codes for languages and countries, as can be retrieved from a java.util.Locale + // instance. The variant value is encoded as the variant string retrieved from a + // java.util.Locale instance built with that variant data. + // @param lang pointer to the ISO three letter code for the language + // @param country pointer to the ISO three letter code for the country + // @param variant pointer to the variant code // @return TTS_SUCCESS, or TTS_FAILURE - virtual tts_result loadLanguage(const char *value, const size_t size); + virtual tts_result loadLanguage(const char *lang, const char *country, const char *variant); // Load the resources associated with the specified language, country and Locale variant. // The loaded language will only be used once a call to setLanguageFromLocale() with the same @@ -112,16 +133,26 @@ public: // @return TTS_SUCCESS, or TTS_FAILURE virtual tts_result setLanguage(const char *lang, const char *country, const char *variant); - // Retrieve the currently set language, or an empty "value" if no language - // has been set. - // @param[out] value pointer to the retrieved language value - // @param[inout] iosize in: stores the size available to store the language - // value in *value - // out: stores the size required to hold the language - // value if getLanguage() returned - // TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise. - // @return TTS_SUCCESS, or TTS_PROPERTY_SIZE_TOO_SMALL, or TTS_FAILURE - virtual tts_result getLanguage(char *value, size_t *iosize); + // Retrieve the currently set language, country and variant, or empty strings if none of + // parameters have been set. Language and country are represented by their 3-letter ISO code + // @param[out] pointer to the retrieved 3-letter code language value + // @param[out] pointer to the retrieved 3-letter code country value + // @param[out] pointer to the retrieved variant value + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result getLanguage(char *language, char *country, char *variant); + + // Notifies the engine what audio parameters should be used for the synthesis. + // This is meant to be used as a hint, the engine implementation will set the output values + // to those of the synthesis format, based on a given hint. + // @param[inout] encoding in: the desired audio sample format + // out: the format used by the TTS engine + // @param[inout] rate in: the desired audio sample rate + // out: the sample rate used by the TTS engine + // @param[inout] channels in: the desired number of audio channels + // out: the number of channels used by the TTS engine + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result setAudioFormat(AudioSystem::audio_format& encoding, uint32_t& rate, + int& channels); // Set a property for the the TTS engine // "size" is the maximum size of "value" for properties "property" diff --git a/include/ui/Camera.h b/include/ui/Camera.h index bbc21c4..97e0e90 100644 --- a/include/ui/Camera.h +++ b/include/ui/Camera.h @@ -63,16 +63,12 @@ namespace android { #define FRAME_CALLBACK_FLAG_CAMERA 0x05 #define FRAME_CALLBACK_FLAG_BARCODE_SCANNER 0x07 -// msgType in notifyCallback function +// msgType in notifyCallback and dataCallback functions enum { - CAMERA_MSG_ERROR, + CAMERA_MSG_ERROR = 0, CAMERA_MSG_SHUTTER, CAMERA_MSG_FOCUS, - CAMERA_MSG_ZOOM -}; - -// msgType in dataCallback function -enum { + CAMERA_MSG_ZOOM, CAMERA_MSG_PREVIEW_FRAME, CAMERA_MSG_VIDEO_FRAME, CAMERA_MSG_POSTVIEW_FRAME, diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index f1029b7..5c41ead 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -866,7 +866,7 @@ struct ResTable_config uint8_t keyboard; uint8_t navigation; uint8_t inputFlags; - uint8_t pad0; + uint8_t inputPad0; }; uint32_t input; }; @@ -905,6 +905,23 @@ struct ResTable_config uint32_t version; }; + enum { + SCREENLAYOUT_ANY = 0x0000, + SCREENLAYOUT_SMALL = 0x0001, + SCREENLAYOUT_NORMAL = 0x0002, + SCREENLAYOUT_LARGE = 0x0003, + }; + + union { + struct { + uint8_t screenLayout; + uint8_t screenConfigPad0; + uint8_t screenConfigPad1; + uint8_t screenConfigPad2; + }; + uint32_t screenConfig; + }; + inline void copyFromDeviceNoSwap(const ResTable_config& o) { const size_t size = dtohl(o.size); if (size >= sizeof(ResTable_config)) { @@ -950,6 +967,8 @@ struct ResTable_config diff = (int32_t)(screenSize - o.screenSize); if (diff != 0) return diff; diff = (int32_t)(version - o.version); + if (diff != 0) return diff; + diff = (int32_t)(screenLayout - o.screenLayout); return (int)diff; } @@ -967,7 +986,8 @@ struct ResTable_config CONFIG_ORIENTATION = 0x0080, CONFIG_DENSITY = 0x0100, CONFIG_SCREEN_SIZE = 0x0200, - CONFIG_VERSION = 0x0400 + CONFIG_VERSION = 0x0400, + CONFIG_SCREEN_LAYOUT = 0x0800 }; // Compare two configuration, returning CONFIG_* flags set for each value @@ -985,6 +1005,7 @@ struct ResTable_config if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION; if (screenSize != o.screenSize) diffs |= CONFIG_SCREEN_SIZE; if (version != o.version) diffs |= CONFIG_VERSION; + if (screenLayout != o.screenLayout) diffs |= CONFIG_SCREEN_LAYOUT; return diffs; } @@ -1062,6 +1083,13 @@ struct ResTable_config } } + if (screenConfig || o.screenConfig) { + if (screenLayout != o.screenLayout) { + if (!screenLayout) return false; + if (!o.screenLayout) return true; + } + } + if (version || o.version) { if (sdkVersion != o.sdkVersion) { if (!sdkVersion) return false; @@ -1191,6 +1219,12 @@ struct ResTable_config } } + if (screenConfig || o.screenConfig) { + if ((screenLayout != o.screenLayout) && requested->screenLayout) { + return (screenLayout); + } + } + if (version || o.version) { if ((sdkVersion != o.sdkVersion) && requested->sdkVersion) { return (sdkVersion); @@ -1282,6 +1316,12 @@ struct ResTable_config return false; } } + if (screenConfig != 0) { + if (settings.screenLayout != 0 && screenLayout != 0 + && screenLayout != settings.screenLayout) { + return false; + } + } if (version != 0) { if (settings.sdkVersion != 0 && sdkVersion != 0 && sdkVersion != settings.sdkVersion) { @@ -1310,13 +1350,13 @@ struct ResTable_config String8 toString() const { char buf[200]; - sprintf(buf, "imsi=%d/%d lang=%c%c reg=%c%c orient=0x%02x touch=0x%02x dens=0x%02x " - "kbd=0x%02x nav=0x%02x input=0x%02x screenW=0x%04x screenH=0x%04x vers=%d.%d", + sprintf(buf, "imsi=%d/%d lang=%c%c reg=%c%c orient=%d touch=%d dens=%d " + "kbd=%d nav=%d input=%d scrnW=%d scrnH=%d layout=%d vers=%d.%d", mcc, mnc, language[0] ? language[0] : '-', language[1] ? language[1] : '-', country[0] ? country[0] : '-', country[1] ? country[1] : '-', orientation, touchscreen, density, keyboard, navigation, inputFlags, - screenWidth, screenHeight, sdkVersion, minorVersion); + screenWidth, screenHeight, screenLayout, sdkVersion, minorVersion); return String8(buf); } }; diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h index 734fba7..5f551df 100644 --- a/libs/rs/RenderScript.h +++ b/libs/rs/RenderScript.h @@ -173,6 +173,14 @@ enum RsTexEnvMode { RS_TEX_ENV_MODE_DECAL }; +enum RsPrimitive { + RS_PRIMITIVE_POINT, + RS_PRIMITIVE_LINE, + RS_PRIMITIVE_LINE_STRIP, + RS_PRIMITIVE_TRIANGLE, + RS_PRIMITIVE_TRIANGLE_STRIP, + RS_PRIMITIVE_TRIANGLE_FAN +}; #include "rsgApiFuncDecl.h" diff --git a/libs/rs/java/Film/res/raw/filmstrip.c b/libs/rs/java/Film/res/raw/filmstrip.c index a3b3d90..1687a31 100644 --- a/libs/rs/java/Film/res/raw/filmstrip.c +++ b/libs/rs/java/Film/res/raw/filmstrip.c @@ -31,6 +31,10 @@ int main(int index) int trans; // float int rot; // float int x; + float focusPos; // float + int focusID; + int lastFocusID; + int imgCount; float_2 = intToFloat(2); float_1 = intToFloat(1); @@ -58,15 +62,17 @@ int main(int index) //int imgId = 0; -/* - contextBindProgramFragmentStore(env->fsImages); - contextBindProgramFragment(env->fpImages); + contextBindProgramFragmentStore(NAMED_PFImages); + contextBindProgramFragment(NAMED_PFSImages); + + //focusPos = loadF(1, 2); + //focusID = 0; + //lastFocusID = loadI32(2, 0); + //imgCount = 13; + + /* disable(GL_LIGHTING); - float focusPos = loadEnvF(1, 2); - int focusID = 0; - int lastFocusID = loadEnvI32(2, 0); - int imgCount = 13; if (trans > (-.3)) { focusID = -1.0 - focusPos; diff --git a/libs/rs/java/Rollo/res/raw/rollo.c b/libs/rs/java/Rollo/res/raw/rollo.c index 56ee425..e6acc9c 100644 --- a/libs/rs/java/Rollo/res/raw/rollo.c +++ b/libs/rs/java/Rollo/res/raw/rollo.c @@ -5,10 +5,58 @@ int main(void* con, int ft, int launchID) { + int rowCount; int x; + int y; + int row; + int col; + int imageID; + int tx1; + int ty1; + int tz1; + int tx2; + int ty2; + int tz2; + int rot; + int rotStep; + int tmpSin; + int tmpCos; + int iconCount; + int pressure; - renderTriangleMesh(con, NAMED_MeshCard); - renderTriangleMesh(con, NAMED_MeshTab); + + iconCount = 38;//loadI32(0, 1); + rotStep = 20 * 0x10000; + pressure = loadI32(0, 2); + + rowCount = 4; + rot = (-20 + loadI32(0, 0)) * 0x10000; + + while (iconCount) { + tmpSin = sinx(rot); + tmpCos = cosx(rot); + + tx1 = tmpSin * 8 - tmpCos; + tx2 = tx1 + tmpCos * 2; + + tz1 = tmpCos * 8 + tmpSin + pressure; + tz2 = tz1 - tmpSin * 2; + + for (y = 0; (y < rowCount) && iconCount; y++) { + ty1 = (y * 0x30000) - 0x48000; + ty2 = ty1 + 0x20000; + + drawQuad(tx1, ty1, tz1, + tx2, ty1, tz2, + tx2, ty2, tz2, + tx1, ty2, tz1); + iconCount--; + } + rot = rot + rotStep; + } + + //renderTriangleMesh(con, NAMED_MeshCard); + //renderTriangleMesh(con, NAMED_MeshTab); return 1; } diff --git a/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java b/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java index da0b146..0f7e26b 100644 --- a/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java +++ b/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java @@ -48,6 +48,12 @@ public class RolloRS { initRS(); } + public void setPosition(float dx, float pressure) { + mAllocStateBuf[0] += (int)(dx); + mAllocStateBuf[2] = (int)(pressure * 0x40000); + mAllocState.data(mAllocStateBuf); + } + private Resources mRes; private RenderScript mRS; @@ -63,23 +69,13 @@ public class RolloRS { private RenderScript.ProgramVertex mPV; private ProgramVertexAlloc mPVAlloc; - private RenderScript.Allocation mAllocEnv; - private RenderScript.Allocation mAllocPos; + private int[] mAllocStateBuf; private RenderScript.Allocation mAllocState; - //private RenderScript.Allocation mAllocPV; - private RenderScript.TriangleMesh mMeshCard; - private RenderScript.TriangleMesh mMeshTab; private float[] mBufferPos; //private float[] mBufferPV; private void initNamed() { - mMeshTab = RolloMesh.createTab(mRS); - mMeshTab.setName("MeshTab"); - mMeshCard = RolloMesh.createCard(mRS); - mMeshCard.setName("MeshCard"); - Log.e("rs", "Done loading strips"); - mRS.samplerBegin(); mRS.samplerSet(RenderScript.SamplerParam.FILTER_MIN, RenderScript.SamplerValue.LINEAR_MIP_LINEAR); @@ -122,6 +118,8 @@ public class RolloRS { mPV.setName("PV"); mPV.bindAllocation(0, mPVAlloc.mAlloc); + + mPVAlloc.setupProjectionNormalized(320, 480); //mPVAlloc.setupOrthoNormalized(320, 480); mRS.contextBindProgramVertex(mPV); @@ -131,6 +129,7 @@ public class RolloRS { } + private void initRS() { mRS.scriptCBegin(); mRS.scriptCSetClearColor(0.0f, 0.7f, 0.0f, 1.0f); @@ -138,6 +137,11 @@ public class RolloRS { mRS.scriptCSetRoot(true); mScript = mRS.scriptCCreate(); + mAllocStateBuf = new int[] {0, 38, 0}; + mAllocState = mRS.allocationCreatePredefSized( + RenderScript.ElementPredefined.USER_I32, mAllocStateBuf.length); + mScript.bindAllocation(mAllocState, 0); + setPosition(0, 0); mRS.contextBindRootScript(mScript); } diff --git a/libs/rs/java/Rollo/src/com/android/rollo/RolloView.java b/libs/rs/java/Rollo/src/com/android/rollo/RolloView.java index 9a30aed..27d2dd6 100644 --- a/libs/rs/java/Rollo/src/com/android/rollo/RolloView.java +++ b/libs/rs/java/Rollo/src/com/android/rollo/RolloView.java @@ -74,6 +74,11 @@ public class RolloView extends RSSurfaceView { if (act == ev.ACTION_UP) { ret = false; } + float x = ev.getX(); + x = (x - 180) / 40; + //Log.e("rs", Float(x).toString()); + + mRender.setPosition(x, ev.getPressure()); //mRender.newTouchPosition((int)ev.getX(), (int)ev.getY()); return ret; } diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h index 4d96a77..a7fd17c 100644 --- a/libs/rs/rsContext.h +++ b/libs/rs/rsContext.h @@ -24,6 +24,7 @@ #include "rsMatrix.h" #include "rsAllocation.h" #include "rsTriangleMesh.h" +#include "rsMesh.h" #include "rsDevice.h" #include "rsScriptC.h" #include "rsAllocation.h" diff --git a/libs/rs/rsMesh.cpp b/libs/rs/rsMesh.cpp new file mode 100644 index 0000000..6eb95fc --- /dev/null +++ b/libs/rs/rsMesh.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rsContext.h" + +using namespace android; +using namespace android::renderscript; + +#include <GLES/gl.h> +#include <GLES/glext.h> + +Mesh::Mesh() +{ + mSources = NULL; + mPrimitives = NULL; + mPrimitiveCount = 0; +} + +Mesh::~Mesh() +{ +} + + + +MeshContext::MeshContext() +{ +} + +MeshContext::~MeshContext() +{ +} + diff --git a/libs/rs/rsMesh.h b/libs/rs/rsMesh.h new file mode 100644 index 0000000..c6d3bc9 --- /dev/null +++ b/libs/rs/rsMesh.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_RS_MESH_H +#define ANDROID_RS_MESH_H + + +#include "RenderScript.h" + +// --------------------------------------------------------------------------- +namespace android { +namespace renderscript { + + +// An element is a group of Components that occupies one cell in a structure. +class Mesh : public ObjectBase +{ +public: + Mesh(); + ~Mesh(); + + struct VertexSource_t + { + const Element * mVertexElement; + void * mVertexData; + size_t mVertexDataSize; + + size_t mOffsetCoord; + size_t mOffsetTex; + size_t mOffsetNorm; + + size_t mSizeCoord; + size_t mSizeTex; + size_t mSizeNorm; + + uint32_t mBufferObject; + }; + + struct Primitive_t + { + RsPrimitive mType; + const Element * mIndexElement; + void * mVertexData; + size_t mIndexDataSize; + + uint32_t mBufferObject; + }; + + VertexSource_t * mSources; + Primitive_t * mPrimitives; + uint32_t mPrimitiveCount; + + void analyzeElement(); +protected: +}; + +class MeshContext +{ +public: + MeshContext(); + ~MeshContext(); + +}; + + +} +} +#endif //ANDROID_RS_TRIANGLE_MESH_H + + diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp index 522ed00..9c99e2b 100644 --- a/libs/rs/rsScriptC.cpp +++ b/libs/rs/rsScriptC.cpp @@ -307,6 +307,60 @@ extern "C" void drawRect(int32_t x1, int32_t x2, int32_t y1, int32_t y2) glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } +extern "C" void drawQuad(int32_t x1, int32_t y1, int32_t z1, + int32_t x2, int32_t y2, int32_t z2, + int32_t x3, int32_t y3, int32_t z3, + int32_t x4, int32_t y4, int32_t z4) +{ + GET_TLS(); + //x1 = (x1 << 16); + //x2 = (x2 << 16); + //y1 = (y1 << 16); + //y2 = (y2 << 16); + + //LOGE("Quad"); + //LOGE("0x%08x, 0x%08x, 0x%08x", x1, y1, z1); + //LOGE("0x%08x, 0x%08x, 0x%08x", x2, y2, z2); + //LOGE("0x%08x, 0x%08x, 0x%08x", x3, y3, z3); + //LOGE("0x%08x, 0x%08x, 0x%08x", x4, y4, z4); + + int32_t vtx[] = {x1,y1,z1, x4,y4,z4, x3,y3,z3, x2,y2,z2}; + static const int32_t tex[] = {0,0, 0,0x10000, 0x10000,0, 0x10000,0x10000}; + + + rsc->setupCheck(); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tm->mBufferObjects[1]); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glVertexPointer(3, GL_FIXED, 0, vtx); + glTexCoordPointer(2, GL_FIXED, 0, tex); + //glColorPointer(4, GL_UNSIGNED_BYTE, 12, ptr); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); +} + +extern "C" int32_t sinx(int32_t angle) +{ + float a = ((float)angle) / 0x10000; + a *= 3.14f / 180.f; + float s = (float)sin(a); + return int32_t(s * 0x10000); +} + +extern "C" int32_t cosx(int32_t angle) +{ + float a = ((float)angle) / 0x10000; + a *= 3.14f / 180.f; + float s = (float)cos(a); + return int32_t(s * 0x10000); +} + extern "C" void pfBindTexture(RsProgramFragment vpf, uint32_t slot, RsAllocation va) { GET_TLS(); diff --git a/libs/surfaceflinger/VRamHeap.cpp b/libs/surfaceflinger/VRamHeap.cpp index 68c0a5e..3b1b152 100644 --- a/libs/surfaceflinger/VRamHeap.cpp +++ b/libs/surfaceflinger/VRamHeap.cpp @@ -55,7 +55,7 @@ namespace android { * (PMEM is used for 2D acceleration) * 8 MB of address space per client should be enough. */ -static const int PMEM_SIZE = int(8 * 1024 * 1024); +static const int PMEM_SIZE = int(16 * 1024 * 1024); int SurfaceHeapManager::global_pmem_heap = 0; @@ -79,7 +79,11 @@ void SurfaceHeapManager::onFirstRef() mPMemHeap = new PMemHeap(device, PMEM_SIZE); if (mPMemHeap->base() == MAP_FAILED) { mPMemHeap.clear(); - global_pmem_heap = 0; + mPMemHeap = new PMemHeap(device, PMEM_SIZE/2); + if (mPMemHeap->base() == MAP_FAILED) { + mPMemHeap.clear(); + global_pmem_heap = 0; + } } } } diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk index 38ef8b9..49939ca 100644 --- a/libs/ui/Android.mk +++ b/libs/ui/Android.mk @@ -24,8 +24,7 @@ LOCAL_SRC_FILES:= \ Region.cpp \ Surface.cpp \ SurfaceComposerClient.cpp \ - SurfaceFlingerSynchro.cpp \ - Time.cpp + SurfaceFlingerSynchro.cpp LOCAL_SHARED_LIBRARIES := \ libcutils \ diff --git a/libs/ui/Camera.cpp b/libs/ui/Camera.cpp index 33b99b9..a72d2c9 100644 --- a/libs/ui/Camera.cpp +++ b/libs/ui/Camera.cpp @@ -149,21 +149,21 @@ status_t Camera::unlock() status_t Camera::setPreviewDisplay(const sp<Surface>& surface) { LOGV("setPreviewDisplay"); - if (surface == 0) { - LOGE("app passed NULL surface"); - return NO_INIT; - } sp <ICamera> c = mCamera; if (c == 0) return NO_INIT; - return c->setPreviewDisplay(surface->getISurface()); + if (surface != 0) { + return c->setPreviewDisplay(surface->getISurface()); + } else { + LOGD("app passed NULL surface"); + return c->setPreviewDisplay(0); + } } status_t Camera::setPreviewDisplay(const sp<ISurface>& surface) { LOGV("setPreviewDisplay"); if (surface == 0) { - LOGE("app passed NULL surface"); - return NO_INIT; + LOGD("app passed NULL surface"); } sp <ICamera> c = mCamera; if (c == 0) return NO_INIT; @@ -171,7 +171,7 @@ status_t Camera::setPreviewDisplay(const sp<ISurface>& surface) } -// start preview mode, must call setPreviewDisplay first +// start preview mode status_t Camera::startPreview() { LOGV("startPreview"); diff --git a/libs/utils/BackupData.cpp b/libs/utils/BackupData.cpp index 0868cff..cce754a 100644 --- a/libs/utils/BackupData.cpp +++ b/libs/utils/BackupData.cpp @@ -107,7 +107,10 @@ BackupDataWriter::WriteEntityHeader(const String8& key, size_t dataSize) } else { k = key; } - LOGD("m_keyPrefix=%s key=%s k=%s", m_keyPrefix.string(), key.string(), k.string()); + if (true) { + LOGD("Writing entity: prefix='%s' key='%s' dataSize=%d", m_keyPrefix.string(), key.string(), + dataSize); + } entity_header_v1 header; ssize_t keyLen; diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp index 99a4abc..4ad9b51 100644 --- a/libs/utils/BackupHelpers.cpp +++ b/libs/utils/BackupHelpers.cpp @@ -68,11 +68,15 @@ struct file_metadata_v1 { const static int CURRENT_METADATA_VERSION = 1; -#if 1 // TEST_BACKUP_HELPERS +#if 1 +#define LOGP(f, x...) +#else +#if TEST_BACKUP_HELPERS #define LOGP(f, x...) printf(f "\n", x) #else #define LOGP(x...) LOGD(x) #endif +#endif const static int ROUND_UP[4] = { 0, 3, 2, 1 }; @@ -349,7 +353,6 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD err = stat(file, &st); if (err != 0) { - LOGW("Error stating file %s", file); r.deleted = true; } else { r.deleted = false; diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index e4f9f0f..7a33220 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -3919,7 +3919,7 @@ void ResTable::print(bool inclValues) const printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); continue; } - printf(" config %d lang=%c%c cnt=%c%c orien=%d touch=%d density=%d key=%d infl=%d nav=%d w=%d h=%d\n", + printf(" config %d lang=%c%c cnt=%c%c orien=%d touch=%d density=%d key=%d infl=%d nav=%d w=%d h=%d lyt=%d\n", (int)configIndex, type->config.language[0] ? type->config.language[0] : '-', type->config.language[1] ? type->config.language[1] : '-', @@ -3932,7 +3932,8 @@ void ResTable::print(bool inclValues) const type->config.inputFlags, type->config.navigation, dtohs(type->config.screenWidth), - dtohs(type->config.screenHeight)); + dtohs(type->config.screenHeight), + type->config.screenLayout); size_t entryCount = dtohl(type->entryCount); uint32_t entriesStart = dtohl(type->entriesStart); if ((entriesStart&0x3) != 0) { diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java index edd1ea0..883e5f5 100755 --- a/location/java/com/android/internal/location/GpsLocationProvider.java +++ b/location/java/com/android/internal/location/GpsLocationProvider.java @@ -38,6 +38,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.provider.Settings; import android.util.Config; import android.util.Log; import android.util.SparseIntArray; @@ -183,7 +184,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { // number of fixes we have received since we started navigating private int mFixCount; - private int mPositionMode = GPS_POSITION_MODE_STANDALONE; + private boolean mAgpsConfigured; // true if we started navigation private boolean mStarted; @@ -355,8 +356,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { try { int port = Integer.parseInt(portString); native_set_agps_server(AGPS_TYPE_SUPL, host, port); - // use MS-Based position mode if SUPL support is enabled - mPositionMode = GPS_POSITION_MODE_MS_BASED; + mAgpsConfigured = true; } catch (NumberFormatException e) { Log.e(TAG, "unable to parse SUPL_PORT: " + portString); } @@ -368,8 +368,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { try { int port = Integer.parseInt(portString); native_set_agps_server(AGPS_TYPE_C2K, host, port); - // use MS-Based position mode if SUPL support is enabled - mPositionMode = GPS_POSITION_MODE_MS_BASED; + mAgpsConfigured = true; } catch (NumberFormatException e) { Log.e(TAG, "unable to parse C2K_PORT: " + portString); } @@ -719,7 +718,15 @@ public class GpsLocationProvider extends ILocationProvider.Stub { if (!mStarted) { if (DEBUG) Log.d(TAG, "startNavigating"); mStarted = true; - if (!native_start(mPositionMode, false, mFixInterval)) { + int positionMode; + if (mAgpsConfigured && Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ASSISTED_GPS_ENABLED, 0) != 0) { + positionMode = GPS_POSITION_MODE_MS_BASED; + } else { + positionMode = GPS_POSITION_MODE_STANDALONE; + } + + if (!native_start(positionMode, false, mFixInterval)) { mStarted = false; Log.e(TAG, "native_start failed in startNavigating()"); return; diff --git a/media/libmediaplayerservice/VorbisPlayer.cpp b/media/libmediaplayerservice/VorbisPlayer.cpp index 14fd6ce..7f0ef21 100644 --- a/media/libmediaplayerservice/VorbisPlayer.cpp +++ b/media/libmediaplayerservice/VorbisPlayer.cpp @@ -345,9 +345,6 @@ status_t VorbisPlayer::reset() { LOGV("reset\n"); Mutex::Autolock l(mMutex); - if (mState != STATE_OPEN) { - return NO_ERROR; - } return reset_nosync(); } @@ -355,10 +352,13 @@ status_t VorbisPlayer::reset() status_t VorbisPlayer::reset_nosync() { // close file - ov_clear(&mVorbisFile); // this also closes the FILE if (mFile != NULL) { - LOGV("OOPS! Vorbis didn't close the file"); - fclose(mFile); + ov_clear(&mVorbisFile); // this also closes the FILE + if (mFile != NULL) { + LOGV("OOPS! Vorbis didn't close the file"); + fclose(mFile); + mFile = NULL; + } } mState = STATE_ERROR; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java index fd3a4ba..e76967d 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java @@ -501,4 +501,6 @@ public class MediaNames { "http://sridharg.googlejunta.com/yslau/stress_media/mp3_regular.mp3"; public static final String STREAM_MPEG4_QVGA_128k = "http://sridharg.googlejunta.com/yslau/stress_media/mpeg4_qvga_24fps.3gp"; + public static final int STREAM_H264_480_360_1411k_DURATION = 46000; + public static final int VIDEO_H263_AAC_DURATION = 501000; } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java index 12eacd3..ae9e102 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java @@ -19,6 +19,7 @@ package com.android.mediaframeworktest; import android.test.InstrumentationTestRunner; import android.test.InstrumentationTestSuite; import com.android.mediaframeworktest.stress.MediaRecorderStressTest; +import com.android.mediaframeworktest.stress.MediaPlayerStressTest; import junit.framework.TestSuite; @@ -28,6 +29,7 @@ public class MediaRecorderStressTestRunner extends InstrumentationTestRunner { public TestSuite getAllTests() { TestSuite suite = new InstrumentationTestSuite(this); suite.addTestSuite(MediaRecorderStressTest.class); + suite.addTestSuite(MediaPlayerStressTest.class); return suite; } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java new file mode 100644 index 0000000..5e213d7 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java @@ -0,0 +1,136 @@ +/* + * 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.mediaframeworktest.stress; + +import com.android.mediaframeworktest.MediaFrameworkTest; + +import android.hardware.Camera; +import android.media.MediaPlayer; +import android.media.MediaRecorder; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; +import android.view.SurfaceHolder; + +import com.android.mediaframeworktest.MediaNames; + +import java.util.Random; + +/** + * Junit / Instrumentation test case for the media player + */ +public class MediaPlayerStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaPlayerStressTest"; + private MediaRecorder mRecorder; + private Camera mCamera; + + private static final int NUMBER_OF_RANDOM_REPOSITION_AND_PLAY = 10; + private static final int NUMBER_OF_RANDOM_REPOSITION_AND_PLAY_SHORT = 5; + private static final int NUMBER_OF_STRESS_LOOPS = 1000; + private static final int PLAYBACK_END_TOLERANCE = 5000; + private static final int WAIT_UNTIL_PLAYBACK_FINISH = 515000 ; + + public MediaPlayerStressTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + protected void setUp() throws Exception { + getActivity(); + super.setUp(); + } + + @LargeTest + public void testStressHWDecoderRelease() throws Exception { + SurfaceHolder mSurfaceHolder; + long randomseed = System.currentTimeMillis(); + Random generator = new Random(randomseed); + Log.v(TAG, "Random seed: " + randomseed); + int video_duration = MediaNames.STREAM_H264_480_360_1411k_DURATION; + int random_play_time; + + mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); + try { + for (int i = 0; i < NUMBER_OF_STRESS_LOOPS; i++) { + MediaPlayer mp = new MediaPlayer(); + mp.setDataSource(MediaNames.STREAM_H264_480_360_1411k); + mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder()); + mp.prepare(); + mp.start(); + // seek and play + for (int j = 0; j < generator.nextInt(10); j++) { + random_play_time = + generator.nextInt(MediaNames.STREAM_H264_480_360_1411k_DURATION / 2); + Log.v(TAG, "Play time = " + random_play_time); + Thread.sleep(random_play_time); + int seek_time = MediaNames.STREAM_H264_480_360_1411k_DURATION / 2; + Log.v(TAG, "Seek time = " + seek_time); + mp.seekTo(seek_time); + } + mp.release(); + } + + } catch (Exception e) { + Log.v(TAG, e.toString()); + } + } + + @LargeTest + public void testStressGetCurrentPosition() throws Exception { + SurfaceHolder mSurfaceHolder; + long randomseed = System.currentTimeMillis(); + Random generator = new Random(randomseed); + Log.v(TAG, "Random seed: " + randomseed); + int video_duration = MediaNames.VIDEO_H263_AAC_DURATION; + int random_play_time = 0; + int random_seek_time = 0; + + mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); + try { + for (int i = 0; i < NUMBER_OF_STRESS_LOOPS; i++) { + MediaPlayer mp = new MediaPlayer(); + mp.setDataSource(MediaNames.VIDEO_H263_AMR); + mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder()); + mp.prepare(); + mp.start(); + // Random seek and play + for (int j = 0; j < generator.nextInt(10); j++) { + random_play_time = + generator.nextInt(video_duration / 2); + Log.v(TAG, "Play time = " + random_play_time); + Thread.sleep(random_play_time); + random_seek_time = + generator.nextInt(video_duration / 2); + Log.v(TAG, "Seek time = " + random_seek_time); + mp.seekTo(random_seek_time); + } + //wait until the movie finish and check the current position + //Make sure the wait time is long enough + long wait_until_playback_finish = video_duration - random_seek_time + PLAYBACK_END_TOLERANCE * 2; + Thread.sleep(wait_until_playback_finish); + Log.v(TAG, "CurrentPosition = " + mp.getCurrentPosition()); + if ( mp.isPlaying() || mp.getCurrentPosition() > (video_duration + PLAYBACK_END_TOLERANCE)){ + assertTrue("Current PlayTime greater than duration", false); + } + mp.release(); + } + + } catch (Exception e) { + Log.v(TAG, e.toString()); + } + } +} + diff --git a/obex/Android.mk b/obex/Android.mk new file mode 100644 index 0000000..fbfe9be --- /dev/null +++ b/obex/Android.mk @@ -0,0 +1,9 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_MODULE:= javax.obex + +include $(BUILD_JAVA_LIBRARY) diff --git a/obex/javax/obex/ApplicationParameter.java b/obex/javax/obex/ApplicationParameter.java new file mode 100644 index 0000000..e808360 --- /dev/null +++ b/obex/javax/obex/ApplicationParameter.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +/** + * @hide + */ +public final class ApplicationParameter { + + private byte[] b_array; + private int length; + private int max_length = 1000; + + public static class TRIPLET_TAGID { + public static final byte ORDER_TAGID = 0x01; + + public static final byte SEARCH_VALUE_TAGID = 0x02; + + public static final byte SEARCH_ATTRIBUTE_TAGID = 0x03; + + // if equals to "0", PSE only reply number of contacts + public static final byte MAXLISTCOUNT_TAGID = 0x04; + + public static final byte LISTSTARTOFFSET_TAGID = 0x05; + + public static final byte FILTER_TAGID = 0x06; + + public static final byte FORMAT_TAGID = 0x07; + + // only used if max list count = 0 + public static final byte PHONEBOOKSIZE_TAGID = 0x08; + + // only used in "mch" in response + public static final byte NEWMISSEDCALLS_TAGID = 0x09; + } + + public static class TRIPLET_VALUE { + public static class ORDER { + public static final byte ORDER_BY_INDEX = 0x00; + + public static final byte ORDER_BY_ALPHANUMERIC = 0x01; + + public static final byte ORDER_BY_PHONETIC = 0x02; + } + + public static class SEARCHATTRIBUTE { + public static final byte SEARCH_BY_NAME = 0x00; + + public static final byte SEARCH_BY_NUMBER = 0x01; + + public static final byte SEARCH_BY_SOUND = 0x02; + } + + public static class FORMAT { + public static final byte VCARD_VERSION_21 = 0x00; + + public static final byte VCARD_VERSION_30 = 0x01; + } + } + + public static class TRIPLET_LENGTH { + public static final byte ORDER_LENGTH = 1; + + //public final byte SEARCH_VALUE_LENGTH = 0x02; + public static final byte SEARCH_ATTRIBUTE_LENGTH = 1; + + public static final byte MAXLISTCOUNT_LENGTH = 2; + + public static final byte LISTSTARTOFFSET_LENGTH = 2; + + public static final byte FILTER_LENGTH = 8; + + public static final byte FORMAT_LENGTH = 1; + + public static final byte PHONEBOOKSIZE_LENGTH = 2; + + public static final byte NEWMISSEDCALLS_LENGTH = 1; + } + + /* + public class TRIPLET_STRUCTURE{ + TRIPLET_TAGID id; + TRIPLET_LENGTH length; + byte[] value; + } + */ + public ApplicationParameter() { + b_array = new byte[max_length]; + length = 0; + } + + public void addAPPHeader(byte tag, byte len, byte[] value) { + if ((length + len + 2) > max_length) { + byte[] array_tmp = new byte[length + 4 * len]; + System.arraycopy(b_array, 0, array_tmp, 0, length); + b_array = array_tmp; + max_length = length + 4 * len; + } + b_array[length++] = tag; + b_array[length++] = len; + System.arraycopy(value, 0, b_array, length, len); + length += len; + } + + public byte[] getAPPparam() { + byte[] para = new byte[length]; + System.arraycopy(b_array, 0, para, 0, length); + return para; + } +} diff --git a/obex/javax/obex/Authenticator.java b/obex/javax/obex/Authenticator.java new file mode 100644 index 0000000..90da7ba --- /dev/null +++ b/obex/javax/obex/Authenticator.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +/** + * This interface provides a way to respond to authentication challenge and + * authentication response headers. When a client or server receives an + * authentication challenge or authentication response header, the + * <code>onAuthenticationChallenge()</code> or + * <code>onAuthenticationResponse()</code> will be called, respectively, by + * the implementation. + * <P> + * For more information on how the authentication procedure works in OBEX, + * please review the IrOBEX specification at + * <A HREF="http://www.irda.org">http://www.irda.org</A>. + * <P> + * <STRONG>Authentication Challenges</STRONG> + * <P> + * When a client or server receives an authentication challenge header, the + * <code>onAuthenticationChallenge()</code> method will be invoked by the + * OBEX API implementation. The application will then return the user name + * (if needed) and password via a <code>PasswordAuthentication</code> object. + * The password in this object is not sent in the authentication response. + * Instead, the 16-byte challenge received in the authentication challenge is + * combined with the password returned from the + * <code>onAuthenticationChallenge()</code> method and passed through the MD5 + * hash algorithm. The resulting value is sent in the authentication response + * along with the user name if it was provided. + * <P> + * <STRONG>Authentication Responses</STRONG> + * <P> + * When a client or server receives an authentication response header, the + * <code>onAuthenticationResponse()</code> method is invoked by the API + * implementation with the user name received in the authentication response + * header. (The user name will be <code>null</code> if no user name was + * provided in the authentication response header.) The application must + * determine the correct password. This value should be returned from the + * <code>onAuthenticationResponse()</code> method. If the authentication + * request should fail without the implementation checking the password, + * <code>null</code> should + * be returned by the application. (This is needed for reasons like not + * recognizing the user name, etc.) If the returned value is not + * <code>null</code>, the OBEX API implementation will combine the password + * returned from the <code>onAuthenticationResponse()</code> method and + * challenge sent via the authentication challenge, apply the MD5 hash + * algorithm, and compare the result to the response hash received in the + * authentication response header. If the values are not equal, an + * <code>IOException</code> will be thrown if the client requested authentication. + * If the server requested authentication, the + * <code>onAuthenticationFailure()</code> method will be called on the + * <code>ServerRequestHandler</code> that failed authentication. The + * connection is <B>not</B> closed if authentication failed. + * + * @hide + */ +public interface Authenticator { + + /** + * Called when a client or a server receives an authentication challenge + * header. It should respond to the challenge with a + * <code>PasswordAuthentication</code> that contains the correct user name + * and password for the challenge. + * + * @param description the description of which user name and password + * should be used; if no description is provided in the authentication + * challenge or the description is encoded in an encoding scheme that is + * not supported, an empty string will be provided + * + * @param isUserIdRequired <code>true</code> if the user ID is required; + * <code>false</code> if the user ID is not required + * + * @param isFullAccess <code>true</code> if full access to the server + * will be granted; <code>false</code> if read only access will be + * granted + * + * @return a <code>PasswordAuthentication</code> object containing the + * user name and password used for authentication + */ + public PasswordAuthentication onAuthenticationChallenge(String description, + boolean isUserIdRequired, boolean isFullAccess); + + /** + * Called when a client or server receives an authentication response + * header. This method will provide the user name and expect the correct + * password to be returned. + * + * @param userName the user name provided in the authentication response; + * may be <code>null</code> + * + * @return the correct password for the user name provided; if + * <code>null</code> is returned then the authentication request failed + */ + public byte[] onAuthenticationResponse(byte[] userName); +} diff --git a/obex/javax/obex/BaseStream.java b/obex/javax/obex/BaseStream.java new file mode 100644 index 0000000..67581bf --- /dev/null +++ b/obex/javax/obex/BaseStream.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.IOException; + +/** + * This interface defines the methods needed by a parent that uses the + * PrivateInputStream and PrivateOutputStream objects defined in this package. + * + * @hide + */ +public interface BaseStream { + + /** + * Verifies that this object is still open. + * + * @throws IOException if the object is closed + */ + public void ensureOpen() throws IOException; + + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + * + * @throws IOException if the operation is completed + */ + public void ensureNotDone() throws IOException; + + /** + * Continues the operation since there is no data to read. + * + * @param sendEmpty <code>true</code> if the operation should send an + * empty packet or not send anything if there is no data to send + * @param inStream <code>true</code> if the stream is input stream or + * is output stream + * @return <code>true</code> if the operation was completed; + * <code>false</code> if no operation took place + * + * @throws IOException if an IO error occurs + */ + public boolean continueOperation(boolean sendEmpty, boolean inStream) throws IOException; + + /** + * Called when the output or input stream is closed. + * + * @param inStream <code>true</code> if the input stream is closed; + * <code>false</code> if the output stream is closed + * + * @throws IOException if an IO error occurs + */ + public void streamClosed(boolean inStream) throws IOException; +} diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java new file mode 100644 index 0000000..5bc302a --- /dev/null +++ b/obex/javax/obex/ClientOperation.java @@ -0,0 +1,840 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; + +/** + * This class implements the <code>Operation</code> interface. It will read + * and write data via puts and gets. + * + * @hide + */ +public final class ClientOperation implements Operation, BaseStream { + + /** + * Defines the basic packet length used by OBEX. Event OBEX packet has the + * same basic format:<BR> + * Byte 0: Request or Response Code + * Byte 1&2: Length of the packet. + */ + private static final int BASE_PACKET_LENGTH = 3; + + private ClientSession parent; + + private InputStream socketInput; + + private PrivateInputStream privateInput; + + private PrivateOutputStream privateOutput; + + private boolean isClosed; + + private String exceptionMessage; + + private int maxPacketSize; + + private boolean isDone; + + private boolean isGet; + + private HeaderSet requestHeaders; + + private HeaderSet replyHeaders; + + private boolean isEndOfBodySent; + + private boolean inputStreamOpened; + + private boolean outputStreamOpened; + + private boolean isValidateConnected; + + /** + * Creates new OperationImpl to read and write data to a server + * + * @param in the input stream to read from + * + * @param maxSize the maximum packet size + * + * @param p the parent to this object + * + * @param headers the headers to set in the initial request + * + * @param type <code>true</code> if this is a get request; + * <code>false</code. if this is a put request + * + * @throws IOExcpetion if the an IO error occured + */ + public ClientOperation(InputStream in, int maxSize, ClientSession p, HeaderSet header, + boolean type) throws IOException { + + parent = p; + isEndOfBodySent = false; + socketInput = in; + isClosed = false; + isDone = false; + maxPacketSize = maxSize; + isGet = type; + + inputStreamOpened = false; + outputStreamOpened = false; + isValidateConnected = false; + + privateInput = null; + privateOutput = null; + + replyHeaders = new HeaderSet(); + + requestHeaders = new HeaderSet(); + + int[] headerList = header.getHeaderList(); + + if (headerList != null) { + + for (int i = 0; i < headerList.length; i++) { + requestHeaders.setHeader(headerList[i], header.getHeader(headerList[i])); + } + } + + if ((header).authChall != null) { + requestHeaders.authChall = new byte[(header).authChall.length]; + System.arraycopy((header).authChall, 0, requestHeaders.authChall, 0, + (header).authChall.length); + } + + if ((header).authResp != null) { + requestHeaders.authResp = new byte[(header).authResp.length]; + System.arraycopy((header).authResp, 0, requestHeaders.authResp, 0, + (header).authResp.length); + + } + // requestHeaders = (HeaderSet)header; + } + + /** + * Sends an ABORT message to the server. By calling this method, the + * corresponding input and output streams will be closed along with this + * object. + * + * @throws IOException if the transaction has already ended or if an + * OBEX server called this method + */ + public synchronized void abort() throws IOException { + ensureOpen(); + // need check again . + // if(isDone) { + // throw new IOException("Operation has already ended"); + // } + + //no compatible with sun-ri + if ((isDone) && (replyHeaders.responseCode != ObexHelper.OBEX_HTTP_CONTINUE)) { + throw new IOException("Operation has already ended"); + } + + exceptionMessage = "Operation aborted"; + if ((!isDone) && (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE)) { + isDone = true; + /* + * Since we are not sending any headers or returning any headers then + * we just need to write and read the same bytes + */ + parent.sendRequest(0xFF, null, replyHeaders, null); + + if (replyHeaders.responseCode != ResponseCodes.OBEX_HTTP_OK) { + throw new IOException("Invalid response code from server"); + } + + exceptionMessage = null; + } + + close(); + } + + /** + * Retrieves the response code retrieved from the server. Response codes + * are defined in the <code>ResponseCodes</code> interface. + * + * @return the response code retrieved from the server + * + * @throws IOException if an error occurred in the transport layer during + * the transaction; if this method is called on a <code>HeaderSet</code> + * object created by calling <code>createHeaderSet</code> in a + * <code>ClientSession</code> object + */ + public synchronized int getResponseCode() throws IOException { + //avoid dup validateConnection + if ((replyHeaders.responseCode == -1) + || (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE)) { + validateConnection(); + } + + return replyHeaders.responseCode; + } + + /** + * This method will always return <code>null</code> + * + * @return <code>null</code> + */ + public String getEncoding() { + return null; + } + + /** + * Returns the type of content that the resource connected to is providing. + * E.g. if the connection is via HTTP, then the value of the content-type + * header field is returned. + * + * @return the content type of the resource that the URL references, or + * <code>null</code> if not known + */ + public String getType() { + try { + return (String)replyHeaders.getHeader(HeaderSet.TYPE); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the length of the content which is being provided. E.g. if the + * connection is via HTTP, then the value of the content-length header + * field is returned. + * + * @return the content length of the resource that this connection's URL + * references, or -1 if the content length is not known + */ + public long getLength() { + try { + Long temp = (Long)replyHeaders.getHeader(HeaderSet.LENGTH); + + if (temp == null) { + return -1; + } else { + return temp.longValue(); + } + } catch (IOException e) { + return -1; + } + } + + /** + * Open and return an input stream for a connection. + * + * @return an input stream + * + * @throws IOException if an I/O error occurs + */ + public InputStream openInputStream() throws IOException { + // TODO: this mode is not set yet. + // if ((parent.mode & Connector.READ) == 0) + // throw new IOException("write-only connection"); + + ensureOpen(); + + if (inputStreamOpened) + throw new IOException("no more input streams available"); + if (isGet) { + // send the GET request here + validateConnection(); + isValidateConnected = true; + } else { + if (privateInput == null) { + privateInput = new PrivateInputStream(this); + } + } + + inputStreamOpened = true; + + return privateInput; + } + + /**8 + * Open and return a data input stream for a connection. + * + * @return an input stream + * + * @throws IOException if an I/O error occurs + */ + public DataInputStream openDataInputStream() throws IOException { + return new DataInputStream(openInputStream()); + } + + /** + * Open and return an output stream for a connection. + * + * @return an output stream + * + * @throws IOException if an I/O error occurs + */ + public OutputStream openOutputStream() throws IOException { + // TODO: this mode is not set yet. + // if ((parent.mode & Connector.WRITE) == 0) + // throw new IOException("read-only connection"); + ensureOpen(); + ensureNotDone(); + + if (outputStreamOpened) + throw new IOException("no more output streams available"); + + if (privateOutput == null) { + // there are 3 bytes operation headers and 3 bytes body headers // + privateOutput = new PrivateOutputStream(this, maxPacketSize - 6); + } + + outputStreamOpened = true; + + return privateOutput; + } + + public int getMaxPacketSize() { + return maxPacketSize - 6; + } + + /** + * Open and return a data output stream for a connection. + * + * @return an output stream + * + * @throws IOException if an I/O error occurs + */ + public DataOutputStream openDataOutputStream() throws IOException { + return new DataOutputStream(openOutputStream()); + } + + /** + * Closes the connection and ends the transaction + * + * @throws IOException if the operation has already ended or is closed + */ + public void close() throws IOException { + isClosed = true; + inputStreamOpened = false; + outputStreamOpened = false; + parent.setRequestInactive(); + } + + /** + * Returns the headers that have been received during the operation. + * Modifying the object returned has no effect on the headers that are + * sent or retrieved. + * + * @return the headers received during this <code>Operation</code> + * + * @throws IOException if this <code>Operation</code> has been closed + */ + public HeaderSet getReceivedHeaders() throws IOException { + ensureOpen(); + + return replyHeaders; + } + + /** + * Specifies the headers that should be sent in the next OBEX message that + * is sent. + * + * @param headers the headers to send in the next message + * + * @throws IOException if this <code>Operation</code> has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @throws IllegalArgumentException if <code>headers</code> was not created + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> + * + * @throws NullPointerException if <code>headers</code> is <code>null</code> + */ + public void sendHeaders(HeaderSet headers) throws IOException { + ensureOpen(); + if (isDone) { + throw new IOException("Operation has already exchanged all data"); + } + + if (headers == null) { + throw new NullPointerException("Headers may not be null"); + } + + int[] headerList = headers.getHeaderList(); + if (headerList != null) { + for (int i = 0; i < headerList.length; i++) { + requestHeaders.setHeader(headerList[i], headers.getHeader(headerList[i])); + } + } + } + + /** + * Reads a response from the server. It will populate the appropriate body + * and headers. + * + * @return <code>true</code> if the transaction should end; + * <code>false</code> if the transaction should not end + * + * @throws IOException if an IO error occurred + */ + private boolean readResponse() throws IOException { + replyHeaders.responseCode = socketInput.read(); + int packetLength = socketInput.read(); + packetLength = (packetLength << 8) + socketInput.read(); + + if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) { + if (exceptionMessage != null) { + abort(); + } + throw new IOException("Received a packet that was too big"); + } + + if (packetLength > BASE_PACKET_LENGTH) { + int dataLength = packetLength - BASE_PACKET_LENGTH; + byte[] data = new byte[dataLength]; + int readLength = socketInput.read(data); + if (readLength != dataLength) { + throw new IOException("Received a packet without data as decalred length"); + } + byte[] body = ObexHelper.updateHeaderSet(replyHeaders, data); + + if (body != null) { + privateInput.writeBytes(body, 1); + + /* + * Determine if a body (0x48) header or an end of body (0x49) + * was received. If we received an end of body and + * a response code of OBEX_HTTP_OK, then the operation should + * end. + */ + if ((body[0] == 0x49) && (replyHeaders.responseCode == ResponseCodes.OBEX_HTTP_OK)) { + return false; + } + } + } + + if (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE) { + return true; + } else { + return false; + } + } + + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + * + * @throws IOException if the operation is completed + */ + public void ensureNotDone() throws IOException { + if (isDone) { + throw new IOException("Operation has completed"); + } + } + + /** + * Verifies that the connection is open and no exceptions should be thrown. + * + * @throws IOException if an exception needs to be thrown + */ + public void ensureOpen() throws IOException { + parent.ensureOpen(); + + if (exceptionMessage != null) { + throw new IOException(exceptionMessage); + } + if (isClosed) { + throw new IOException("Operation has already ended"); + } + } + + /** + * Verifies that the connection is open and the proper data has been read. + * + * @throws IOException if an IO error occurs + */ + private void validateConnection() throws IOException { + ensureOpen(); + + // to sure only one privateInput object exist. + if (privateInput == null) { + startProcessing(); + } + } + + /** + * Sends a request to the client of the specified type + * + * @param response the response code to send back to the client + * + * @return <code>true</code> if there is more data to send; + * <code>false</code> if there is no more data to send + * + * @throws IOException if an IO error occurs + */ + protected boolean sendRequest(int type) throws IOException { + boolean returnValue = false; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int bodyLength = -1; + byte[] headerArray = ObexHelper.createHeader(requestHeaders, true); + if (privateOutput != null) { + bodyLength = privateOutput.size(); + } + + /* + * Determine if there is space to add a body request. At present + * this method checks to see if there is room for at least a 17 + * byte body header. This number needs to be at least 6 so that + * there is room for the header ID and length and the reply ID and + * length, but it is a waste of resources if we can't send much of + * the body. + */ + if ((BASE_PACKET_LENGTH + headerArray.length) > maxPacketSize) { + int end = 0; + int start = 0; + // split & send the headerArray in multiple packets. + + while (end != headerArray.length) { + //split the headerArray + end = ObexHelper.findHeaderEnd(headerArray, start, maxPacketSize + - BASE_PACKET_LENGTH); + // can not split + if (end == -1) { + isDone = true; + abort(); + // isDone = true; + exceptionMessage = "Header larger then can be sent in a packet"; + isClosed = true; + + if (privateInput != null) { + privateInput.close(); + } + + if (privateOutput != null) { + privateOutput.close(); + } + throw new IOException("OBEX Packet exceeds max packet size"); + } + + byte[] sendHeader = new byte[end - start]; + System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); + if (!parent.sendRequest(type, sendHeader, replyHeaders, privateInput)) { + return false; + } + + if (replyHeaders.responseCode != ObexHelper.OBEX_HTTP_CONTINUE) { + return false; + } + + start = end; + } + + if (bodyLength > 0) { + return true; + } else { + return false; + } + } else { + out.write(headerArray); + } + + if (bodyLength > 0) { + /* + * Determine if I can send the whole body or just part of + * the body. Remember that there is the 3 bytes for the + * response message and 3 bytes for the header ID and length + */ + if (bodyLength > (maxPacketSize - headerArray.length - 6)) { + returnValue = true; + + bodyLength = maxPacketSize - headerArray.length - 6; + } + + byte[] body = privateOutput.readBytes(bodyLength); + + /* + * Since this is a put request if the final bit is set or + * the output stream is closed we need to send the 0x49 + * (End of Body) otherwise, we need to send 0x48 (Body) + */ + if ((privateOutput.isClosed()) && (!returnValue) && (!isEndOfBodySent) + && ((type & 0x80) != 0)) { + out.write(0x49); + isEndOfBodySent = true; + } else { + out.write(0x48); + } + + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + + if (body != null) { + out.write(body); + } + } + + if (outputStreamOpened && bodyLength <= 0 && !isEndOfBodySent) { + // only 0x82 or 0x83 can send 0x49 + if ((type & 0x80) == 0) { + out.write(0x48); + } else { + out.write(0x49); + isEndOfBodySent = true; + + } + + bodyLength = 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + } + + if (out.size() == 0) { + if (!parent.sendRequest(type, null, replyHeaders, privateInput)) { + return false; + } + return returnValue; + } + if ((out.size() > 0) + && (!parent.sendRequest(type, out.toByteArray(), replyHeaders, privateInput))) { + return false; + } + + // send all of the output data in 0x48, + // send 0x49 with empty body + if ((privateOutput != null) && (privateOutput.size() > 0)) + returnValue = true; + + return returnValue; + } + + /** + * This method starts the processing thread results. It will send the + * initial request. If the response takes more then one packet, a thread + * will be started to handle additional requests + * + * @throws IOException if an IO error occurs + */ + private synchronized void startProcessing() throws IOException { + + if (privateInput == null) { + privateInput = new PrivateInputStream(this); + } + boolean more = true; + + if (isGet) { + if (!isDone) { + replyHeaders.responseCode = ObexHelper.OBEX_HTTP_CONTINUE; + while ((more) && (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x03); + } + + if (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE) { + parent.sendRequest(0x83, null, replyHeaders, privateInput); + } + if (replyHeaders.responseCode != ObexHelper.OBEX_HTTP_CONTINUE) { + isDone = true; + } + } + } else { + + if (!isDone) { + replyHeaders.responseCode = ObexHelper.OBEX_HTTP_CONTINUE; + while ((more) && (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x02); + + } + } + + if (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE) { + parent.sendRequest(0x82, null, replyHeaders, privateInput); + } + + if (replyHeaders.responseCode != ObexHelper.OBEX_HTTP_CONTINUE) { + isDone = true; + } + } + } + + /** + * Continues the operation since there is no data to read. + * + * @param sendEmpty <code>true</code> if the operation should send an + * empty packet or not send anything if there is no data to send + * @param inStream <code>true</code> if the stream is input stream or + * is output stream + * @throws IOException if an IO error occurs + */ + public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) + throws IOException { + + if (isGet) { + if ((inStream) && (!isDone)) { + // to deal with inputstream in get operation + parent.sendRequest(0x83, null, replyHeaders, privateInput); + /* + * Determine if that was not the last packet in the operation + */ + if (replyHeaders.responseCode != ObexHelper.OBEX_HTTP_CONTINUE) { + isDone = true; + } + + return true; + + } else if ((!inStream) && (!isDone)) { + // to deal with outputstream in get operation + + if (privateInput == null) { + privateInput = new PrivateInputStream(this); + } + sendRequest(0x03); + return true; + + } else if (isDone) { + return false; + } + + } else { + if ((!inStream) && (!isDone)) { + // to deal with outputstream in put operation + if (replyHeaders.responseCode == -1) { + replyHeaders.responseCode = ObexHelper.OBEX_HTTP_CONTINUE; + } + sendRequest(0x02); + return true; + } else if ((inStream) && (!isDone)) { + // How to deal with inputstream in put operation ? + return false; + + } else if (isDone) { + return false; + } + + } + return false; + } + + /** + * Called when the output or input stream is closed. + * + * @param inStream <code>true</code> if the input stream is closed; + * <code>false</code> if the output stream is closed + * + * @throws IOException if an IO error occurs + */ + public void streamClosed(boolean inStream) throws IOException { + if (!isGet) { + if ((!inStream) && (!isDone)) { + // to deal with outputstream in put operation + + boolean more = true; + + if ((privateOutput != null) && (privateOutput.size() <= 0)) { + byte[] headerArray = ObexHelper.createHeader(requestHeaders, false); + if (headerArray.length <= 0) + more = false; + } + // If have not sent any data so send all now + if (replyHeaders.responseCode == -1) { + replyHeaders.responseCode = ObexHelper.OBEX_HTTP_CONTINUE; + } + + while ((more) && (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x02); + } + + /* + * According to the IrOBEX specification, after the final put, you + * only have a single reply to send. so we don't need the while + * loop. + */ + while (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE) { + + sendRequest(0x82); + } + isDone = true; + } else if ((inStream) && (isDone)) { + // how to deal with input stream in put stream ? + isDone = true; + } + } else { + isValidateConnected = false; + if ((inStream) && (!isDone)) { + + // to deal with inputstream in get operation + // Have not sent any data so send it all now + + if (replyHeaders.responseCode == -1) { + replyHeaders.responseCode = ObexHelper.OBEX_HTTP_CONTINUE; + } + + while (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE) { + if (!sendRequest(0x83)) { + break; + } + } + while (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE) { + parent.sendRequest(0x83, null, replyHeaders, privateInput); + } + isDone = true; + } else if ((!inStream) && (!isDone)) { + // to deal with outputstream in get operation + // part of the data may have been sent in continueOperation. + + boolean more = true; + + if ((privateOutput != null) && (privateOutput.size() <= 0)) { + byte[] headerArray = ObexHelper.createHeader(requestHeaders, false); + if (headerArray.length <= 0) + more = false; + } + + if (privateInput == null) { + privateInput = new PrivateInputStream(this); + } + if ((privateOutput != null) && (privateOutput.size() <= 0)) + more = false; + + replyHeaders.responseCode = ObexHelper.OBEX_HTTP_CONTINUE; + while ((more) && (replyHeaders.responseCode == ObexHelper.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x03); + } + sendRequest(0x83); + // parent.sendRequest(0x83, null, replyHeaders, privateInput); + if (replyHeaders.responseCode != ObexHelper.OBEX_HTTP_CONTINUE) { + isDone = true; + } + + } + } + } +} diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java new file mode 100644 index 0000000..f65ded9 --- /dev/null +++ b/obex/javax/obex/ClientSession.java @@ -0,0 +1,723 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * This class implements the <code>Operation</code> interface. It will read + * and write data via puts and gets. + * + * @hide + */ +public final class ClientSession implements ObexSession { + private Authenticator mAuthenticator; + + private boolean mOpen; + + // Determines if an OBEX layer connection has been established + private boolean mObexConnected; + + private byte[] mConnectionId = null; + + private byte[] mChallengeDigest = null; + + /* + * The max Packet size must be at least 256 according to the OBEX + * specification. + */ + private int maxPacketSize = 256; + + private boolean mRequestActive; + + private final InputStream mInput; + + private final OutputStream mOutput; + + public ClientSession(ObexTransport trans) throws IOException { + mInput = trans.openInputStream(); + mOutput = trans.openOutputStream(); + mOpen = true; + mRequestActive = false; + } + + public HeaderSet connect(HeaderSet header) throws IOException { + ensureOpen(); + if (mObexConnected) { + throw new IOException("Already connected to server"); + } + setRequestActive(); + + int totalLength = 4; + byte[] head = null; + + // Determine the header byte array + if (header != null) { + if (header.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16); + } + head = ObexHelper.createHeader(header, false); + totalLength += head.length; + } + /* + * Write the OBEX CONNECT packet to the server. + * Byte 0: 0x80 + * Byte 1&2: Connect Packet Length + * Byte 3: OBEX Version Number (Presently, 0x10) + * Byte 4: Flags (For TCP 0x00) + * Byte 5&6: Max OBEX Packet Length (Defined in MAX_PACKET_SIZE) + * Byte 7 to n: headers + */ + byte[] requestPacket = new byte[totalLength]; + // We just need to start at byte 3 since the sendRequest() method will + // handle the length and 0x80. + requestPacket[0] = (byte)0x10; + requestPacket[1] = (byte)0x00; + requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); + requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); + if (head != null) { + System.arraycopy(head, 0, requestPacket, 4, head.length); + } + + // check with local max packet size + if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { + throw new IOException("Packet size exceeds max packet size"); + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(0x80, requestPacket, returnHeaderSet, null); + + /* + * Read the response from the OBEX server. + * Byte 0: Response Code (If successful then OBEX_HTTP_OK) + * Byte 1&2: Packet Length + * Byte 3: OBEX Version Number + * Byte 4: Flags3 + * Byte 5&6: Max OBEX packet Length + * Byte 7 to n: Optional HeaderSet + */ + if (returnHeaderSet.responseCode == ResponseCodes.OBEX_HTTP_OK) { + mObexConnected = true; + } + setRequestInactive(); + + return returnHeaderSet; + } + + public Operation get(HeaderSet header) throws IOException { + + if (!mObexConnected) { + throw new IOException("Not connected to the server"); + } + setRequestActive(); + + ensureOpen(); + + if (header == null) { + header = new HeaderSet(); + } else { + if (header.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16); + } + } + // Add the connection ID if one exists + if (mConnectionId != null) { + header.connectionID = new byte[4]; + System.arraycopy(mConnectionId, 0, header.connectionID, 0, 4); + } + + return new ClientOperation(mInput, maxPacketSize, this, header, true); + } + + /** + * 0xCB Connection Id an identifier used for OBEX connection multiplexing + */ + public void setConnectionID(long id) { + if ((id < 0) || (id > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Connection ID is not in a valid range"); + } + mConnectionId = ObexHelper.convertToByteArray(id); + } + + public HeaderSet delete(HeaderSet header) throws IOException { + + Operation op = put(header); + op.getResponseCode(); + HeaderSet returnValue = op.getReceivedHeaders(); + op.close(); + + return returnValue; + } + + public HeaderSet disconnect(HeaderSet header) throws IOException { + if (!mObexConnected) { + throw new IOException("Not connected to the server"); + } + setRequestActive(); + + ensureOpen(); + // Determine the header byte array + byte[] head = null; + if (header != null) { + if (header.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16); + } + // Add the connection ID if one exists + if (mConnectionId != null) { + header.connectionID = new byte[4]; + System.arraycopy(mConnectionId, 0, header.connectionID, 0, 4); + } + head = ObexHelper.createHeader(header, false); + + if ((head.length + 3) > maxPacketSize) { + throw new IOException("Packet size exceeds max packet size"); + } + } else { + // Add the connection ID if one exists + if (mConnectionId != null) { + head = new byte[5]; + head[0] = (byte)0xCB; + System.arraycopy(mConnectionId, 0, head, 1, 4); + } + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(0x81, head, returnHeaderSet, null); + + /* + * An OBEX DISCONNECT reply from the server: + * Byte 1: Response code + * Bytes 2 & 3: packet size + * Bytes 4 & up: headers + */ + + /* response code , and header are ignored + * */ + + synchronized (this) { + mObexConnected = false; + setRequestInactive(); + } + + return returnHeaderSet; + } + + public long getConnectionID() { + + if (mConnectionId == null) { + return -1; + } + return ObexHelper.convertToLong(mConnectionId); + } + + public Operation put(HeaderSet header) throws IOException { + if (!mObexConnected) { + throw new IOException("Not connected to the server"); + } + setRequestActive(); + + ensureOpen(); + + if (header == null) { + header = new HeaderSet(); + } else { + // when auth is initated by client ,save the digest + if (header.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16); + } + } + + // Add the connection ID if one exists + if (mConnectionId != null) { + + header.connectionID = new byte[4]; + System.arraycopy(mConnectionId, 0, header.connectionID, 0, 4); + } + + return new ClientOperation(mInput, maxPacketSize, this, header, false); + } + + public void setAuthenticator(Authenticator auth) { + if (auth == null) { + throw new NullPointerException("Authenticator may not be null"); + } + mAuthenticator = auth; + } + + public HeaderSet setPath(HeaderSet header, boolean backup, boolean create) + throws IOException { + if (!mObexConnected) { + throw new IOException("Not connected to the server"); + } + setRequestActive(); + ensureOpen(); + + int totalLength = 2; + byte[] head = null; + + if (header == null) { + header = new HeaderSet(); + } else { + if (header.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16); + } + } + + // when auth is initiated by client ,save the digest + if (header.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16); + } + + // Add the connection ID if one exists + if (mConnectionId != null) { + header.connectionID = new byte[4]; + System.arraycopy(mConnectionId, 0, header.connectionID, 0, 4); + } + + head = ObexHelper.createHeader(header, false); + totalLength += head.length; + + if (totalLength > maxPacketSize) { + throw new IOException("Packet size exceeds max packet size"); + } + + int flags = 0; + /* + * The backup flag bit is bit 0 so if we add 1, this will set that bit + */ + if (backup) { + flags++; + } + /* + * The create bit is bit 1 so if we or with 2 the bit will be set. + */ + if (!create) { + flags |= 2; + } + + /* + * An OBEX SETPATH packet to the server: + * Byte 1: 0x85 + * Byte 2 & 3: packet size + * Byte 4: flags + * Byte 5: constants + * Byte 6 & up: headers + */ + byte[] packet = new byte[totalLength]; + packet[0] = (byte)flags; + packet[1] = (byte)0x00; + if (header != null) { + System.arraycopy(head, 0, packet, 2, head.length); + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(0x85, packet, returnHeaderSet, null); + + /* + * An OBEX SETPATH reply from the server: + * Byte 1: Response code + * Bytes 2 & 3: packet size + * Bytes 4 & up: headers + */ + + setRequestInactive(); + + return returnHeaderSet; + } + + /** + * Verifies that the connection is open. + * + * @throws IOException if the connection is closed + */ + public synchronized void ensureOpen() throws IOException { + if (!mOpen) { + throw new IOException("Connection closed"); + } + } + + /** + * Set request inactive. + * Allows Put and get operation objects to tell this object when they are + * done. + */ + /*package*/ synchronized void setRequestInactive() { + mRequestActive = false; + } + + /** + * Set request to active. + * @throws IOException if already active + */ + private synchronized void setRequestActive() throws IOException { + if (mRequestActive) { + throw new IOException("OBEX request is already being performed"); + } + mRequestActive = true; + } + + /** + * Sends a standard request to the client. It will then wait for the reply + * and update the header set object provided. If any authentication + * headers (i.e. authentication challenge or authentication response) are + * received, they will be processed. + * + * @param code the type of request to send to the client + * + * @param head the headers to send to the server + * + * @param challenge the nonce that was sent in the authentication + * challenge header located in <code>head</code>; <code>null</code> + * if no authentication header is included in <code>head</code> + * + * @param header the header object to update with the response + * + * @param input the input stream used by the Operation object; null if this + * is called on a CONNECT, SETPATH or DISCONNECT + * + * return <code>true</code> if the operation completed successfully; + * <code>false</code> if an authentication response failed to pass + * + * @throws IOException if an IO error occurs + */ + public boolean sendRequest(int code, byte[] head, HeaderSet header, + PrivateInputStream privateInput) throws IOException { + //check header length with local max size + if (head != null) { + if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { + throw new IOException("header too large "); + } + } + //byte[] nonce; + int bytesReceived; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write((byte)code); + + // Determine if there are any headers to send + if (head == null) { + out.write(0x00); + out.write(0x03); + } else { + out.write((byte)((head.length + 3) >> 8)); + out.write((byte)(head.length + 3)); + out.write(head); + } + + // Write the request to the output stream and flush the stream + mOutput.write(out.toByteArray()); + mOutput.flush(); + + header.responseCode = mInput.read(); + + int length = ((mInput.read() << 8) | (mInput.read())); + + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + throw new IOException("Packet received exceeds packet size limit"); + } + if (length > 3) { + byte[] data = null; + if (code == 0x80) { + int version = mInput.read(); + int flags = mInput.read(); + maxPacketSize = (mInput.read() << 8) + mInput.read(); + + //check with local max size + if (maxPacketSize > ObexHelper.MAX_PACKET_SIZE_INT) { + maxPacketSize = ObexHelper.MAX_PACKET_SIZE_INT; + } + + if (length > 7) { + data = new byte[length - 7]; + + bytesReceived = mInput.read(data); + while (bytesReceived != (length - 7)) { + bytesReceived += mInput.read(data, bytesReceived, data.length + - bytesReceived); + } + } else { + return true; + } + } else { + data = new byte[length - 3]; + bytesReceived = mInput.read(data); + + while (bytesReceived != (length - 3)) { + bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived); + } + if (code == 0xFF) { + return true; + } + } + + byte[] body = ObexHelper.updateHeaderSet(header, data); + if ((privateInput != null) && (body != null)) { + privateInput.writeBytes(body, 1); + } + + if (header.connectionID != null) { + mConnectionId = new byte[4]; + System.arraycopy(header.connectionID, 0, mConnectionId, 0, 4); + } + + if (header.authResp != null) { + if (!handleAuthResp(header.authResp)) { + setRequestInactive(); + throw new IOException("Authentication Failed"); + } + } + + if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED) + && (header.authChall != null)) { + + if (handleAuthChall(header)) { + out.write((byte)0x4E); + out.write((byte)((header.authResp.length + 3) >> 8)); + out.write((byte)(header.authResp.length + 3)); + out.write(header.authResp); + header.authChall = null; + header.authResp = null; + + byte[] sendHeaders = new byte[out.size() - 3]; + System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length); + + return sendRequest(code, sendHeaders, header, privateInput); + } + } + } + + return true; + } + + /** + * Called when the client received an authentication challenge header. This + * will cause the authenticator to handle the authentication challenge. + * + * @param header the header with the authentication challenge + * + * @return <code>true</code> if the last request should be resent; + * <code>false</code> if the last request should not be resent + */ + protected boolean handleAuthChall(HeaderSet header) { + + if (mAuthenticator == null) { + return false; + } + + /* + * An authentication challenge is made up of one required and two + * optional tag length value triplets. The tag 0x00 is required to be + * in the authentication challenge and it represents the challenge + * digest that was received. The tag 0x01 is the options tag. This + * tag tracks if user ID is required and if full access will be + * granted. The tag 0x02 is the realm, which provides a description of + * which user name and password to use. + */ + byte[] challenge = ObexHelper.getTagValue((byte)0x00, header.authChall); + byte[] option = ObexHelper.getTagValue((byte)0x01, header.authChall); + byte[] description = ObexHelper.getTagValue((byte)0x02, header.authChall); + + String realm = ""; + if (description != null) { + byte[] realmString = new byte[description.length - 1]; + System.arraycopy(description, 1, realmString, 0, realmString.length); + + switch (description[0] & 0xFF) { + + case 0x00: + // ASCII encoding + // Fall through + case 0x01: + // ISO-8859-1 encoding + try { + realm = new String(realmString, "ISO8859_1"); + } catch (Exception e) { + throw new RuntimeException("Unsupported Encoding Scheme"); + } + break; + + case 0xFF: + // UNICODE Encoding + realm = ObexHelper.convertToUnicode(realmString, false); + break; + + case 0x02: + // ISO-8859-2 encoding + // Fall through + case 0x03: + // ISO-8859-3 encoding + // Fall through + case 0x04: + // ISO-8859-4 encoding + // Fall through + case 0x05: + // ISO-8859-5 encoding + // Fall through + case 0x06: + // ISO-8859-6 encoding + // Fall through + case 0x07: + // ISO-8859-7 encoding + // Fall through + case 0x08: + // ISO-8859-8 encoding + // Fall through + case 0x09: + // ISO-8859-9 encoding + // Fall through + default: + throw new RuntimeException("Unsupported Encoding Scheme"); + } + } + + boolean isUserIDRequired = false; + boolean isFullAccess = true; + if (option != null) { + if ((option[0] & 0x01) != 0) { + isUserIDRequired = true; + } + + if ((option[0] & 0x02) != 0) { + isFullAccess = false; + } + } + + PasswordAuthentication result = null; + header.authChall = null; + + try { + result = mAuthenticator.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); + } catch (Exception e) { + return false; + } + + /* + * If no password was provided then do not resend the request + */ + if (result == null) { + return false; + } + + byte[] password = result.getPassword(); + if (password == null) { + return false; + } + + byte[] userName = result.getUserName(); + + /* + * Create the authentication response header. It includes 1 required + * and 2 option tag length value triples. The required triple has a + * tag of 0x00 and is the response digest. The first optional tag is + * 0x01 and represents the user ID. If no user ID is provided, then + * no user ID will be sent. The second optional tag is 0x02 and is the + * challenge that was received. This will always be sent + */ + if (userName != null) { + header.authResp = new byte[38 + userName.length]; + header.authResp[36] = (byte)0x01; + header.authResp[37] = (byte)userName.length; + System.arraycopy(userName, 0, header.authResp, 38, userName.length); + } else { + header.authResp = new byte[36]; + } + + // Create the secret String + byte[] digest = new byte[challenge.length + password.length]; + System.arraycopy(challenge, 0, digest, 0, challenge.length); + System.arraycopy(password, 0, digest, challenge.length, password.length); + + // Add the Response Digest + header.authResp[0] = (byte)0x00; + header.authResp[1] = (byte)0x10; + + byte[] responseDigest = ObexHelper.computeMd5Hash(digest); + System.arraycopy(responseDigest, 0, header.authResp, 2, 16); + + // Add the challenge + header.authResp[18] = (byte)0x02; + header.authResp[19] = (byte)0x10; + System.arraycopy(challenge, 0, header.authResp, 20, 16); + + return true; + } + + /** + * Called when the client received an authentication response header. This + * will cause the authenticator to handle the authentication response. + * + * @param authResp the authentication response + * + * @return <code>true</code> if the response passed; <code>false</code> if + * the response failed + */ + protected boolean handleAuthResp(byte[] authResp) { + if (mAuthenticator == null) { + return false; + } + + byte[] correctPassword = mAuthenticator.onAuthenticationResponse(ObexHelper.getTagValue( + (byte)0x01, authResp)); + if (correctPassword == null) { + return false; + } + + byte[] temp = new byte[correctPassword.length + 16]; + System.arraycopy(mChallengeDigest, 0, temp, 0, 16); + System.arraycopy(correctPassword, 0, temp, 16, correctPassword.length); + + byte[] correctResponse = ObexHelper.computeMd5Hash(temp); + byte[] actualResponse = ObexHelper.getTagValue((byte)0x00, authResp); + for (int i = 0; i < 16; i++) { + if (correctResponse[i] != actualResponse[i]) { + return false; + } + } + + return true; + } + + public void close() throws IOException { + mOpen = false; + mInput.close(); + mOutput.close(); + } +} diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java new file mode 100644 index 0000000..5dddf8f --- /dev/null +++ b/obex/javax/obex/HeaderSet.java @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Calendar; +import java.util.Random; + +/** + * This class implements the javax.obex.HeaderSet interface for OBEX over + * RFCOMM. + * + * @hide + */ +public final class HeaderSet { + + /** + * Represents the OBEX Count header. This allows the connection statement + * to tell the server how many objects it plans to send or retrieve. + * <P> + * The value of <code>COUNT</code> is 0xC0 (192). + */ + public static final int COUNT = 0xC0; + + /** + * Represents the OBEX Name header. This specifies the name of the object. + * <P> + * The value of <code>NAME</code> is 0x01 (1). + */ + public static final int NAME = 0x01; + + /** + * Represents the OBEX Type header. This allows a request to specify the + * type of the object (e.g. text, html, binary, etc.). + * <P> + * The value of <code>TYPE</code> is 0x42 (66). + */ + public static final int TYPE = 0x42; + + /** + * Represents the OBEX Length header. This is the length of the object in + * bytes. + * <P> + * The value of <code>LENGTH</code> is 0xC3 (195). + */ + public static final int LENGTH = 0xC3; + + /** + * Represents the OBEX Time header using the ISO 8601 standards. This is + * the preferred time header. + * <P> + * The value of <code>TIME_ISO_8601</code> is 0x44 (68). + */ + public static final int TIME_ISO_8601 = 0x44; + + /** + * Represents the OBEX Time header using the 4 byte representation. This + * is only included for backwards compatibility. It represents the number + * of seconds since January 1, 1970. + * <P> + * The value of <code>TIME_4_BYTE</code> is 0xC4 (196). + */ + public static final int TIME_4_BYTE = 0xC4; + + /** + * Represents the OBEX Description header. This is a text description of + * the object. + * <P> + * The value of <code>DESCRIPTION</code> is 0x05 (5). + */ + public static final int DESCRIPTION = 0x05; + + /** + * Represents the OBEX Target header. This is the name of the service an + * operation is targeted to. + * <P> + * The value of <code>TARGET</code> is 0x46 (70). + */ + public static final int TARGET = 0x46; + + /** + * Represents the OBEX HTTP header. This allows an HTTP 1.X header to be + * included in a request or reply. + * <P> + * The value of <code>HTTP</code> is 0x47 (71). + */ + public static final int HTTP = 0x47; + + /** + * Represents the OBEX Who header. Identifies the OBEX application to + * determine if the two peers are talking to each other. + * <P> + * The value of <code>WHO</code> is 0x4A (74). + */ + public static final int WHO = 0x4A; + + /** + * Represents the OBEX Object Class header. This header specifies the + * OBEX object class of the object. + * <P> + * The value of <code>OBJECT_CLASS</code> is 0x4F (79). + */ + public static final int OBJECT_CLASS = 0x4F; + + /** + * Represents the OBEX Application Parameter header. This header specifies + * additional application request and response information. + * <P> + * The value of <code>APPLICATION_PARAMETER</code> is 0x4C (76). + */ + public static final int APPLICATION_PARAMETER = 0x4C; + + private Long count; // 4 byte unsigned integer + + private String name; // null terminated Unicode text string + + private String type; // null terminated ASCII text string + + private Long length; // 4 byte unsigend integer + + private Calendar isoTime; // String of the form YYYYMMDDTHHMMSSZ + + private Calendar byteTime; // 4 byte unsigned integer + + private String description; // null terminated Unicode text String + + private byte[] target; // byte sequence + + private byte[] http; // byte sequence + + private byte[] who; // length prefixed byte sequence + + private byte[] appParam; // byte sequence of the form tag length value + + public byte[] authChall; // The authentication challenge header + + public byte[] authResp; // The authentication response header + + public byte[] connectionID; // THe connection ID + + private byte[] objectClass; // byte sequence + + private String[] unicodeUserDefined; //null terminated unicode string + + private byte[][] sequenceUserDefined; // byte sequence user defined + + private Byte[] byteUserDefined; // 1 byte + + private Long[] integerUserDefined; // 4 byte unsigned integer + + /*package*/ int responseCode; + + /*package*/ byte[] nonce; + + private final Random random; + + /** + * Creates new <code>HeaderSet</code> object. + * + * @param size the max packet size for this connection + */ + public HeaderSet() { + unicodeUserDefined = new String[16]; + sequenceUserDefined = new byte[16][]; + byteUserDefined = new Byte[16]; + integerUserDefined = new Long[16]; + responseCode = -1; + random = new Random(); + } + + /** + * Sets the value of the header identifier to the value provided. The type + * of object must correspond to the Java type defined in the description of + * this interface. If <code>null</code> is passed as the + * <code>headerValue</code> then the header will be removed from the set of + * headers to include in the next request. + * + * @param headerID the identifier to include in the message + * + * @param headerValue the value of the header identifier + * + * @throws IllegalArgumentException if the header identifier provided is + * not one defined in this interface or a user-defined header; if the type of + * <code>headerValue</code> is not the correct Java type as defined in the + * description of this interface\ + */ + public void setHeader(int headerID, Object headerValue) { + long temp = -1; + + switch (headerID) { + case COUNT: + if (!(headerValue instanceof Long)) { + if (headerValue == null) { + count = null; + break; + } + throw new IllegalArgumentException("Count must be a Long"); + } + temp = ((Long)headerValue).longValue(); + if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Count must be between 0 and 0xFFFFFFFF"); + } + count = (Long)headerValue; + break; + case NAME: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Name must be a String"); + } + name = (String)headerValue; + break; + case TYPE: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Type must be a String"); + } + type = (String)headerValue; + break; + case LENGTH: + if (!(headerValue instanceof Long)) { + if (headerValue == null) { + length = null; + break; + } + throw new IllegalArgumentException("Length must be a Long"); + } + temp = ((Long)headerValue).longValue(); + if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Length must be between 0 and 0xFFFFFFFF"); + } + length = (Long)headerValue; + break; + case TIME_ISO_8601: + if ((headerValue != null) && (!(headerValue instanceof Calendar))) { + throw new IllegalArgumentException("Time ISO 8601 must be a Calendar"); + } + isoTime = (Calendar)headerValue; + break; + case TIME_4_BYTE: + if ((headerValue != null) && (!(headerValue instanceof Calendar))) { + throw new IllegalArgumentException("Time 4 Byte must be a Calendar"); + } + byteTime = (Calendar)headerValue; + break; + case DESCRIPTION: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Description must be a String"); + } + description = (String)headerValue; + break; + case TARGET: + if (headerValue == null) { + target = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("Target must be a byte array"); + } else { + target = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, target, 0, target.length); + } + } + break; + case HTTP: + if (headerValue == null) { + http = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("HTTP must be a byte array"); + } else { + http = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, http, 0, http.length); + } + } + break; + case WHO: + if (headerValue == null) { + who = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("WHO must be a byte array"); + } else { + who = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, who, 0, who.length); + } + } + break; + case OBJECT_CLASS: + if (headerValue == null) { + objectClass = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("Object Class must be a byte array"); + } else { + objectClass = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, objectClass, 0, objectClass.length); + } + } + break; + case APPLICATION_PARAMETER: + if (headerValue == null) { + appParam = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException( + "Application Parameter must be a byte array"); + } else { + appParam = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, appParam, 0, appParam.length); + } + } + break; + default: + // Verify that it was not a Unicode String user Defined + if ((headerID >= 0x30) && (headerID <= 0x3F)) { + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException( + "Unicode String User Defined must be a String"); + } + unicodeUserDefined[headerID - 0x30] = (String)headerValue; + + break; + } + // Verify that it was not a byte sequence user defined value + if ((headerID >= 0x70) && (headerID <= 0x7F)) { + + if (headerValue == null) { + sequenceUserDefined[headerID - 0x70] = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException( + "Byte Sequence User Defined must be a byte array"); + } else { + sequenceUserDefined[headerID - 0x70] = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, sequenceUserDefined[headerID - 0x70], + 0, sequenceUserDefined[headerID - 0x70].length); + } + } + break; + } + // Verify that it was not a Byte user Defined + if ((headerID >= 0xB0) && (headerID <= 0xBF)) { + if ((headerValue != null) && (!(headerValue instanceof Byte))) { + throw new IllegalArgumentException("ByteUser Defined must be a Byte"); + } + byteUserDefined[headerID - 0xB0] = (Byte)headerValue; + + break; + } + // Verify that is was not the 4 byte unsigned integer user + // defined header + if ((headerID >= 0xF0) && (headerID <= 0xFF)) { + if (!(headerValue instanceof Long)) { + if (headerValue == null) { + integerUserDefined[headerID - 0xF0] = null; + break; + } + throw new IllegalArgumentException("Integer User Defined must be a Long"); + } + temp = ((Long)headerValue).longValue(); + if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { + throw new IllegalArgumentException( + "Integer User Defined must be between 0 and 0xFFFFFFFF"); + } + integerUserDefined[headerID - 0xF0] = (Long)headerValue; + break; + } + throw new IllegalArgumentException("Invalid Header Identifier"); + } + } + + /** + * Retrieves the value of the header identifier provided. The type of the + * Object returned is defined in the description of this interface. + * + * @param headerID the header identifier whose value is to be returned + * + * @return the value of the header provided or <code>null</code> if the + * header identifier specified is not part of this <code>HeaderSet</code> + * object + * + * @throws IllegalArgumentException if the <code>headerID</code> is not + * one defined in this interface or any of the user-defined headers + * + * @throws IOException if an error occurred in the transport layer during + * the operation or if the connection has been closed + */ + public Object getHeader(int headerID) throws IOException { + + switch (headerID) { + case COUNT: + return count; + case NAME: + return name; + case TYPE: + return type; + case LENGTH: + return length; + case TIME_ISO_8601: + return isoTime; + case TIME_4_BYTE: + return byteTime; + case DESCRIPTION: + return description; + case TARGET: + return target; + case HTTP: + return http; + case WHO: + return who; + case OBJECT_CLASS: + return objectClass; + case APPLICATION_PARAMETER: + return appParam; + default: + // Verify that it was not a Unicode String user Defined + if ((headerID >= 0x30) && (headerID <= 0x3F)) { + return unicodeUserDefined[headerID - 0x30]; + } + // Verify that it was not a byte sequence user defined header + if ((headerID >= 0x70) && (headerID <= 0x7F)) { + return sequenceUserDefined[headerID - 0x70]; + } + // Verify that it was not a byte user defined header + if ((headerID >= 0xB0) && (headerID <= 0xBF)) { + return byteUserDefined[headerID - 0xB0]; + } + // Verify that it was not a itneger user defined header + if ((headerID >= 0xF0) && (headerID <= 0xFF)) { + return integerUserDefined[headerID - 0xF0]; + } + throw new IllegalArgumentException("Invalid Header Identifier"); + } + } + + /** + * Retrieves the list of headers that may be retrieved via the + * <code>getHeader</code> method that will not return <code>null</code>. + * In other words, this method returns all the headers that are available + * in this object. + * + * @see #getHeader + * + * @return the array of headers that are set in this object or + * <code>null</code> if no headers are available + * + * @throws IOException if an error occurred in the transport layer during + * the operation or the connection has been closed + */ + public int[] getHeaderList() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + if (count != null) { + out.write(COUNT); + } + if (name != null) { + out.write(NAME); + } + if (type != null) { + out.write(TYPE); + } + if (length != null) { + out.write(LENGTH); + } + if (isoTime != null) { + out.write(TIME_ISO_8601); + } + if (byteTime != null) { + out.write(TIME_4_BYTE); + } + if (description != null) { + out.write(DESCRIPTION); + } + if (target != null) { + out.write(TARGET); + } + if (http != null) { + out.write(HTTP); + } + if (who != null) { + out.write(WHO); + } + if (appParam != null) { + out.write(APPLICATION_PARAMETER); + } + if (objectClass != null) { + out.write(OBJECT_CLASS); + } + + for (int i = 0x30; i < 0x40; i++) { + if (unicodeUserDefined[i - 0x30] != null) { + out.write(i); + } + } + + for (int i = 0x70; i < 0x80; i++) { + if (sequenceUserDefined[i - 0x70] != null) { + out.write(i); + } + } + + for (int i = 0xB0; i < 0xC0; i++) { + if (byteUserDefined[i - 0xB0] != null) { + out.write(i); + } + } + + for (int i = 0xF0; i < 0x100; i++) { + if (integerUserDefined[i - 0xF0] != null) { + out.write(i); + } + } + + byte[] headers = out.toByteArray(); + out.close(); + + if ((headers == null) || (headers.length == 0)) { + return null; + } + + int[] result = new int[headers.length]; + for (int i = 0; i < headers.length; i++) { + // Convert the byte to a positive integer. That is, an integer + // between 0 and 256. + result[i] = headers[i] & 0xFF; + } + + return result; + } + + /** + * Sets the authentication challenge header. The <code>realm</code> will + * be encoded based upon the default encoding scheme used by the + * implementation to encode strings. Therefore, the encoding scheme used + * to encode the <code>realm</code> is application dependent. + * + * @param realm a short description that describes what password to use; if + * <code>null</code> no realm will be sent in the authentication challenge + * header + * + * @param userID if <code>true</code>, a user ID is required in the reply; + * if <code>false</code>, no user ID is required + * + * @param access if <code>true</code> then full access will be granted if + * successful; if <code>false</code> then read-only access will be granted + * if successful + */ + public void createAuthenticationChallenge(String realm, boolean userID, boolean access) { + + try { + nonce = new byte[16]; + for (int i = 0; i < 16; i++) { + nonce[i] = (byte)random.nextInt(); + } + + authChall = ObexHelper.computeAuthenticationChallenge(nonce, realm, access, userID); + } catch (IOException e) { + throw new RuntimeException(e.getMessage()); + } + } + + /** + * Returns the response code received from the server. Response codes + * are defined in the <code>ResponseCodes</code> class. + * + * @see ResponseCodes + * + * @return the response code retrieved from the server + * + * @throws IOException if an error occurred in the transport layer during + * the transaction; if this method is called on a <code>HeaderSet</code> + * object created by calling <code>createHeaderSet()</code> in a + * <code>ClientSession</code> object; if this object was created by an OBEX + * server + */ + public int getResponseCode() throws IOException { + if (responseCode == -1) { + throw new IOException("May not be called on a server"); + } else { + return responseCode; + } + } +} diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java new file mode 100644 index 0000000..6bf5e3e --- /dev/null +++ b/obex/javax/obex/ObexHelper.java @@ -0,0 +1,995 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import android.security.Md5MessageDigest; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * This class defines a set of helper methods for the implementation of Obex. + * + * @hide + */ +public final class ObexHelper { + + /** Prevent object construction of helper class */ + private ObexHelper() {} + + /** + * Defines the OBEX CONTINUE response code. + * <P> + * The value of <code>OBEX_HTTP_CONTINUE</code> is 0x90 (144). + */ + public static final int OBEX_HTTP_CONTINUE = 0x90; + + /** + * The maximum packet size for OBEX packets that this client can handle. + * At present, this must be changed for each port. + * + * TODO: The max packet size should be the Max incoming MTU minus + * TODO: L2CAP package headers and RFCOMM package headers. + * + * TODO: Retrieve the max incoming MTU from + * TODO: LocalDevice.getProperty(). + */ + /** android note + * set as 0xFFFE to match remote MPS + */ + public static final int MAX_PACKET_SIZE_INT = 0xFFFE; + + /** + * Updates the HeaderSet with the headers received in the byte array + * provided. Invalid headers are ignored. + * <P> + * The first two bits of an OBEX Header specifies the type of object that + * is being sent. The table below specifies the meaning of the high + * bits. + * <TABLE> + * <TR><TH>Bits 8 and 7</TH><TH>Value</TH><TH>Description</TH></TR> + * <TR><TD>00</TD><TD>0x00</TD><TD>Null Terminated Unicode text, prefixed + * with 2 byte unsigned integer</TD></TR> + * <TR><TD>01</TD><TD>0x40</TD><TD>Byte Sequence, length prefixed with + * 2 byte unsigned integer</TD></TR> + * <TR><TD>10</TD><TD>0x80</TD><TD>1 byte quantity</TD></TR> + * <TR><TD>11</TD><TD>0xC0</TD><TD>4 byte quantity - transmitted in + * network byte order (high byte first</TD></TR> + * </TABLE> + * This method uses the information in this table to determine the type of + * Java object to create and passes that object with the full header + * to setHeader() to update the HeaderSet object. Invalid headers will + * cause an exception to be thrown. When it is thrown, it is ignored. + * + * @param header the HeaderSet to update + * + * @param headerArray the byte array containing headers + * + * @return the result of the last start body or end body header provided; + * the first byte in the result will specify if a body or end of body is + * received + * + * @throws IOException if an invalid header was found + */ + public static byte[] updateHeaderSet(HeaderSet header, byte[] headerArray) throws IOException { + int index = 0; + int length = 0; + int headerID; + byte[] value = null; + byte[] body = null; + HeaderSet headerImpl = header; + try { + while (index < headerArray.length) { + headerID = 0xFF & headerArray[index]; + switch (headerID & (0xC0)) { + + /* + * 0x00 is a unicode null terminate string with the first + * two bytes after the header identifier being the length + */ + case 0x00: + // Fall through + /* + * 0x40 is a byte sequence with the first + * two bytes after the header identifier being the length + */ + case 0x40: + boolean trimTail = true; + index++; + length = 0xFF & headerArray[index]; + length = length << 8; + index++; + length += 0xFF & headerArray[index]; + length -= 3; + index++; + value = new byte[length]; + System.arraycopy(headerArray, index, value, 0, length); + if (length == 0 || (length > 0 && (value[length - 1] != 0))) { + trimTail = false; + } + switch (headerID) { + case HeaderSet.TYPE: + try { + // Remove trailing null + if (trimTail == false) { + headerImpl.setHeader(headerID, new String(value, 0, + value.length, "ISO8859_1")); + } else { + headerImpl.setHeader(headerID, new String(value, 0, + value.length - 1, "ISO8859_1")); + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("ISO8859_1 is not supported" + + e.getMessage()); + } + break; + + // This is the constant for the authentication challenge header + // This header does not have a constant defined in the Java + // OBEX API + case 0x4D: + headerImpl.authChall = new byte[length]; + System.arraycopy(headerArray, index, headerImpl.authChall, 0, + length); + break; + + // This is the constant for the authentication response header + // This header does not have a constant defined in the Java + // OBEX API + case 0x4E: + headerImpl.authResp = new byte[length]; + System + .arraycopy(headerArray, index, headerImpl.authResp, 0, + length); + break; + + /* + * These two case statements are for the body (0x48) + * and end of body (0x49) headers. + */ + case 0x48: + /* Fall Through */ + case 0x49: + body = new byte[length + 1]; + body[0] = (byte)headerID; + System.arraycopy(headerArray, index, body, 1, length); + break; + + case HeaderSet.TIME_ISO_8601: + try { + String dateString = new String(value, "ISO8859_1"); + Calendar temp = Calendar.getInstance(); + if ((dateString.length() == 16) + && (dateString.charAt(15) == 'Z')) { + temp.setTimeZone(TimeZone.getTimeZone("UTC")); + } + temp.set(Calendar.YEAR, Integer.parseInt(dateString.substring( + 0, 4))); + temp.set(Calendar.MONTH, Integer.parseInt(dateString.substring( + 4, 6))); + temp.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateString + .substring(6, 8))); + temp.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateString + .substring(9, 11))); + temp.set(Calendar.MINUTE, Integer.parseInt(dateString + .substring(11, 13))); + temp.set(Calendar.SECOND, Integer.parseInt(dateString + .substring(13, 15))); + headerImpl.setHeader(HeaderSet.TIME_ISO_8601, temp); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("ISO8859_1 is not supported" + + e.getMessage()); + } catch (Exception e) { + throw new IOException( + "Time Header does not follow ISO 8601 standard"); + } + break; + + default: + try { + if ((headerID & 0xC0) == 0x00) { + headerImpl.setHeader(headerID, ObexHelper.convertToUnicode( + value, true)); + } else { + headerImpl.setHeader(headerID, value); + } + } catch (Exception e) { + // Not a valid header so ignore + } + } + + index += length; + break; + + /* + * 0x80 is a byte header. The only valid byte headers are + * the 16 user defined byte headers. + */ + case 0x80: + index++; + try { + headerImpl.setHeader(headerID, Byte.valueOf(headerArray[index])); + } catch (Exception e) { + // Not a valid header so ignore + } + index++; + break; + + /* + * 0xC0 is a 4 byte unsigned integer header and with the + * exception of TIME_4_BYTE will be converted to a Long + * and added. + */ + case 0xC0: + index++; + value = new byte[4]; + System.arraycopy(headerArray, index, value, 0, 4); + try { + if (headerID != HeaderSet.TIME_4_BYTE) { + // Determine if it is a connection ID. These + // need to be handled differently + if (headerID == 0xCB) { + headerImpl.connectionID = new byte[4]; + System.arraycopy(value, 0, headerImpl.connectionID, 0, 4); + } else { + headerImpl.setHeader(headerID, Long + .valueOf(convertToLong(value))); + } + } else { + Calendar temp = Calendar.getInstance(); + temp.setTime(new Date(convertToLong(value) * 1000L)); + headerImpl.setHeader(HeaderSet.TIME_4_BYTE, temp); + } + } catch (Exception e) { + // Not a valid header so ignore + throw new IOException("Header was not formatted properly"); + } + index += 4; + break; + } + + } + } catch (IOException e) { + throw new IOException("Header was not formatted properly"); + } + + return body; + } + + /** + * Creates the header part of OBEX packet based on the header provided. + * + * TODO: Could use getHeaderList() to get the array of headers to + * TODO: include and then use the high two bits to determine the + * TODO: the type of the object and construct the byte array from + * TODO: that. This will make the size smaller. + * + * @param head the header used to construct the byte array + * + * @param nullOut <code>true</code> if the header should be set to + * <code>null</code> once it is added to the array or <code>false</code> + * if it should not be nulled out + * + * @return the header of an OBEX packet + */ + public static byte[] createHeader(HeaderSet head, boolean nullOut) { + Long intHeader = null; + String stringHeader = null; + Calendar dateHeader = null; + Byte byteHeader = null; + StringBuffer buffer = null; + byte[] value = null; + byte[] result = null; + byte[] lengthArray = new byte[2]; + int length; + HeaderSet headImpl = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (!(head instanceof HeaderSet)) { + throw new IllegalArgumentException("Header not created by createHeaderSet"); + } + headImpl = head; + + try { + /* + * Determine if there is a connection ID to send. If there is, + * then it should be the first header in the packet. + */ + if ((headImpl.connectionID != null) && (headImpl.getHeader(HeaderSet.TARGET) == null)) { + + out.write((byte)0xCB); + out.write(headImpl.connectionID); + } + + // Count Header + intHeader = (Long)headImpl.getHeader(HeaderSet.COUNT); + if (intHeader != null) { + out.write((byte)HeaderSet.COUNT); + value = ObexHelper.convertToByteArray(intHeader.longValue()); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.COUNT, null); + } + } + + // Name Header + stringHeader = (String)headImpl.getHeader(HeaderSet.NAME); + if (stringHeader != null) { + out.write((byte)HeaderSet.NAME); + value = ObexHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.NAME, null); + } + } + + // Type Header + stringHeader = (String)headImpl.getHeader(HeaderSet.TYPE); + if (stringHeader != null) { + out.write((byte)HeaderSet.TYPE); + try { + value = stringHeader.getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unsupported Encoding Scheme: " + e.getMessage()); + } + + length = value.length + 4; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + out.write(0x00); + if (nullOut) { + headImpl.setHeader(HeaderSet.TYPE, null); + } + } + + // Length Header + intHeader = (Long)headImpl.getHeader(HeaderSet.LENGTH); + if (intHeader != null) { + out.write((byte)HeaderSet.LENGTH); + value = ObexHelper.convertToByteArray(intHeader.longValue()); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.LENGTH, null); + } + } + + // Time ISO Header + dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_ISO_8601); + if (dateHeader != null) { + + /* + * The ISO Header should take the form YYYYMMDDTHHMMSSZ. The + * 'Z' will only be included if it is a UTC time. + */ + buffer = new StringBuffer(); + int temp = dateHeader.get(Calendar.YEAR); + for (int i = temp; i < 1000; i = i * 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.MONTH); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.DAY_OF_MONTH); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + buffer.append("T"); + temp = dateHeader.get(Calendar.HOUR_OF_DAY); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.MINUTE); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.SECOND); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + + if (dateHeader.getTimeZone().getID().equals("UTC")) { + buffer.append("Z"); + } + + try { + value = buffer.toString().getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UnsupportedEncodingException: " + e.getMessage()); + } + + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(HeaderSet.TIME_ISO_8601); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TIME_ISO_8601, null); + } + } + + // Time 4 Byte Header + dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_4_BYTE); + if (dateHeader != null) { + out.write(HeaderSet.TIME_4_BYTE); + + /* + * Need to call getTime() twice. The first call will return + * a java.util.Date object. The second call returns the number + * of milliseconds since January 1, 1970. We need to convert + * it to seconds since the TIME_4_BYTE expects the number of + * seconds since January 1, 1970. + */ + value = ObexHelper.convertToByteArray(dateHeader.getTime().getTime() / 1000L); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TIME_4_BYTE, null); + } + } + + // Description Header + stringHeader = (String)headImpl.getHeader(HeaderSet.DESCRIPTION); + if (stringHeader != null) { + out.write((byte)HeaderSet.DESCRIPTION); + value = ObexHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.DESCRIPTION, null); + } + } + + // Target Header + value = (byte[])headImpl.getHeader(HeaderSet.TARGET); + if (value != null) { + out.write((byte)HeaderSet.TARGET); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TARGET, null); + } + } + + // HTTP Header + value = (byte[])headImpl.getHeader(HeaderSet.HTTP); + if (value != null) { + out.write((byte)HeaderSet.HTTP); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.HTTP, null); + } + } + + // Who Header + value = (byte[])headImpl.getHeader(HeaderSet.WHO); + if (value != null) { + out.write((byte)HeaderSet.WHO); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.WHO, null); + } + } + + // Connection ID Header + value = (byte[])headImpl.getHeader(HeaderSet.APPLICATION_PARAMETER); + if (value != null) { + out.write((byte)HeaderSet.APPLICATION_PARAMETER); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.APPLICATION_PARAMETER, null); + } + } + + // Object Class Header + value = (byte[])headImpl.getHeader(HeaderSet.OBJECT_CLASS); + if (value != null) { + out.write((byte)HeaderSet.OBJECT_CLASS); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.OBJECT_CLASS, null); + } + } + + // Check User Defined Headers + for (int i = 0; i < 16; i++) { + + //Unicode String Header + stringHeader = (String)headImpl.getHeader(i + 0x30); + if (stringHeader != null) { + out.write((byte)i + 0x30); + value = ObexHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(i + 0x30, null); + } + } + + // Byte Sequence Header + value = (byte[])headImpl.getHeader(i + 0x70); + if (value != null) { + out.write((byte)i + 0x70); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(i + 0x70, null); + } + } + + // Byte Header + byteHeader = (Byte)headImpl.getHeader(i + 0xB0); + if (byteHeader != null) { + out.write((byte)i + 0xB0); + out.write(byteHeader.byteValue()); + if (nullOut) { + headImpl.setHeader(i + 0xB0, null); + } + } + + // Integer header + intHeader = (Long)headImpl.getHeader(i + 0xF0); + if (intHeader != null) { + out.write((byte)i + 0xF0); + out.write(ObexHelper.convertToByteArray(intHeader.longValue())); + if (nullOut) { + headImpl.setHeader(i + 0xF0, null); + } + } + } + + // Add the authentication challenge header + if (headImpl.authChall != null) { + out.write((byte)0x4D); + length = headImpl.authChall.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(headImpl.authChall); + if (nullOut) { + headImpl.authChall = null; + } + } + + // Add the authentication response header + if (headImpl.authResp != null) { + out.write((byte)0x4E); + length = headImpl.authResp.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(headImpl.authResp); + if (nullOut) { + headImpl.authResp = null; + } + } + + } catch (IOException e) { + } finally { + result = out.toByteArray(); + try { + out.close(); + } catch (Exception ex) { + } + } + + return result; + + } + + /** + * Determines where the maximum divide is between headers. This method is + * used by put and get operations to separate headers to a size that meets + * the max packet size allowed. + * + * @param headerArray the headers to separate + * + * @param start the starting index to search + * + * @param maxSize the maximum size of a packet + * + * @return the index of the end of the header block to send or -1 if the + * header could not be divided because the header is too large + */ + public static int findHeaderEnd(byte[] headerArray, int start, int maxSize) { + + int fullLength = 0; + int lastLength = -1; + int index = start; + int length = 0; + + while ((fullLength < maxSize) && (index < headerArray.length)) { + int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]); + lastLength = fullLength; + + switch (headerID & (0xC0)) { + + case 0x00: + // Fall through + case 0x40: + + index++; + length = (headerArray[index] < 0 ? headerArray[index] + 256 + : headerArray[index]); + length = length << 8; + index++; + length += (headerArray[index] < 0 ? headerArray[index] + 256 + : headerArray[index]); + length -= 3; + index++; + index += length; + fullLength += length + 3; + break; + + case 0x80: + + index++; + index++; + fullLength += 2; + break; + + case 0xC0: + + index += 5; + fullLength += 5; + break; + + } + + } + + /* + * Determine if this is the last header or not + */ + if (lastLength == 0) { + /* + * Since this is the last header, check to see if the size of this + * header is less then maxSize. If it is, return the length of the + * header, otherwise return -1. The length of the header is + * returned since it would be the start of the next header + */ + if (fullLength < maxSize) { + return headerArray.length; + } else { + return -1; + } + } else { + return lastLength + start; + } + } + + /** + * Converts the byte array to a long. + * + * @param b the byte array to convert to a long + * + * @return the byte array as a long + */ + public static long convertToLong(byte[] b) { + long result = 0; + long value = 0; + long power = 0; + + for (int i = (b.length - 1); i >= 0; i--) { + value = b[i]; + if (value < 0) { + value += 256; + } + + result = result | (value << power); + power += 8; + } + + return result; + } + + /** + * Converts the long to a 4 byte array. The long must be non negative. + * + * @param l the long to convert + * + * @return a byte array that is the same as the long + */ + public static byte[] convertToByteArray(long l) { + byte[] b = new byte[4]; + + b[0] = (byte)(255 & (l >> 24)); + b[1] = (byte)(255 & (l >> 16)); + b[2] = (byte)(255 & (l >> 8)); + b[3] = (byte)(255 & l); + + return b; + } + + /** + * Converts the String to a UNICODE byte array. It will also add the ending + * null characters to the end of the string. + * + * @param s the string to convert + * + * @return the unicode byte array of the string + */ + public static byte[] convertToUnicodeByteArray(String s) { + if (s == null) { + return null; + } + + char c[] = s.toCharArray(); + byte[] result = new byte[(c.length * 2) + 2]; + for (int i = 0; i < c.length; i++) { + result[(i * 2)] = (byte)(c[i] >> 8); + result[((i * 2) + 1)] = (byte)c[i]; + } + + // Add the UNICODE null character + result[result.length - 2] = 0; + result[result.length - 1] = 0; + + return result; + } + + /** + * Retrieves the value from the byte array for the tag value specified. The + * array should be of the form Tag - Length - Value triplet. + * + * @param tag the tag to retrieve from the byte array + * + * @param triplet the byte sequence containing the tag length value form + * + * @return the value of the specified tag + */ + public static byte[] getTagValue(byte tag, byte[] triplet) { + + int index = findTag(tag, triplet); + if (index == -1) { + return null; + } + + index++; + int length = triplet[index] & 0xFF; + + byte[] result = new byte[length]; + index++; + System.arraycopy(triplet, index, result, 0, length); + + return result; + } + + /** + * Finds the index that starts the tag value pair in the byte array provide. + * + * @param tag the tag to look for + * + * @param value the byte array to search + * + * @return the starting index of the tag or -1 if the tag could not be found + */ + public static int findTag(byte tag, byte[] value) { + int length = 0; + + if (value == null) { + return -1; + } + + int index = 0; + + while ((index < value.length) && (value[index] != tag)) { + length = value[index + 1] & 0xFF; + index += length + 2; + } + + if (index >= value.length) { + return -1; + } + + return index; + } + + /** + * Converts the byte array provided to a unicode string. + * + * @param b the byte array to convert to a string + * + * @param includesNull determine if the byte string provided contains the + * UNICODE null character at the end or not; if it does, it will be + * removed + * + * @return a Unicode string + * + * @param IllegalArgumentException if the byte array has an odd length + */ + public static String convertToUnicode(byte[] b, boolean includesNull) { + if (b == null) { + return null; + } + int arrayLength = b.length; + if (!((arrayLength % 2) == 0)) { + throw new IllegalArgumentException("Byte array not of a valid form"); + } + arrayLength = (arrayLength >> 1); + if (includesNull) { + arrayLength -= 1; + } + + char[] c = new char[arrayLength]; + for (int i = 0; i < arrayLength; i++) { + int upper = b[2 * i]; + int lower = b[(2 * i) + 1]; + if (upper < 0) { + upper += 256; + } + if (lower < 0) { + lower += 256; + } + + c[i] = (char)((upper << 8) | lower); + } + + return new String(c); + } + + /** + * Compute the MD5 hash of the byte array provided. + * Does not accumulate input. + * + * @param in the byte array to hash + * @return the MD5 hash of the byte array + */ + public static byte[] computeMd5Hash(byte[] in) { + Md5MessageDigest md5 = new Md5MessageDigest(); + return md5.digest(in); + } + + /** + * Computes an authentication challenge header. + * + * + * @param nonce the challenge that will be provided to the peer; the + * challenge must be 16 bytes long + * + * @param realm a short description that describes what password to use + * + * @param access if <code>true</code> then full access will be granted if + * successful; if <code>false</code> then read only access will be granted + * if successful + * + * @param userID if <code>true</code>, a user ID is required in the reply; + * if <code>false</code>, no user ID is required + * + * @throws IllegalArgumentException if the challenge is not 16 bytes + * long; if the realm can not be encoded in less then 255 bytes + * + * @throws IOException if the encoding scheme ISO 8859-1 is not supported + */ + public static byte[] computeAuthenticationChallenge(byte[] nonce, String realm, boolean access, + boolean userID) throws IOException { + byte[] authChall = null; + + if (nonce.length != 16) { + throw new IllegalArgumentException("Nonce must be 16 bytes long"); + } + + /* + * The authentication challenge is a byte sequence of the following form + * byte 0: 0x00 - the tag for the challenge + * byte 1: 0x10 - the length of the challenge; must be 16 + * byte 2-17: the authentication challenge + * byte 18: 0x01 - the options tag; this is optional in the spec, but + * we are going to include it in every message + * byte 19: 0x01 - length of the options; must be 1 + * byte 20: the value of the options; bit 0 is set if user ID is + * required; bit 1 is set if access mode is read only + * byte 21: 0x02 - the tag for authentication realm; only included if + * an authentication realm is specified + * byte 22: the length of the authentication realm; only included if + * the authentication realm is specified + * byte 23: the encoding scheme of the authentication realm; we will use + * the ISO 8859-1 encoding scheme since it is part of the KVM + * byte 24 & up: the realm if one is specified. + */ + if (realm == null) { + authChall = new byte[21]; + } else { + if (realm.length() >= 255) { + throw new IllegalArgumentException("Realm must be less then 255 bytes"); + } + authChall = new byte[24 + realm.length()]; + authChall[21] = 0x02; + authChall[22] = (byte)(realm.length() + 1); + authChall[23] = 0x01; // ISO 8859-1 Encoding + System.arraycopy(realm.getBytes("ISO8859_1"), 0, authChall, 24, realm.length()); + } + + // Include the nonce field in the header + authChall[0] = 0x00; + authChall[1] = 0x10; + System.arraycopy(nonce, 0, authChall, 2, 16); + + // Include the options header + authChall[18] = 0x01; + authChall[19] = 0x01; + authChall[20] = 0x00; + + if (!access) { + authChall[20] = (byte)(authChall[20] | 0x02); + } + if (userID) { + authChall[20] = (byte)(authChall[20] | 0x01); + } + + return authChall; + } +} diff --git a/obex/javax/obex/ObexSession.java b/obex/javax/obex/ObexSession.java new file mode 100644 index 0000000..1dc50c6 --- /dev/null +++ b/obex/javax/obex/ObexSession.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.IOException; + +/** + * The <code>ObexSession</code> interface characterizes the term + * "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which + * could be the server-side view of an OBEX connection, or the client-side view + * of the same connection, which is established by server's accepting of a + * client issued "CONNECT". + * <P> + * + * This interface serves as the common super class for + * <CODE>ClientSession</CODE> and <CODE>ServerSession</CODE>. + * + * @hide + */ +public interface ObexSession { + + public void close() throws IOException; + +} diff --git a/obex/javax/obex/ObexTransport.java b/obex/javax/obex/ObexTransport.java new file mode 100644 index 0000000..d0ba0c9 --- /dev/null +++ b/obex/javax/obex/ObexTransport.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The <code>ObexTransport</code> interface defines the underlying transport + * connection which carries the OBEX protocol( such as TCP, RFCOMM device file + * exposed by Bluetooth or USB in kernel, RFCOMM socket emulated in Android + * platform, Irda). This interface provides an abstract layer to be used by the + * <code>ObexConnection</code>. Each kind of medium shall have its own + * implementation to wrap and follow the same interface. + * <P> + * See section 1.2.2 of IrDA Object Exchange Protocol specification. + * <P> + * Different kind of medium may have different construction - for example, the + * RFCOMM device file medium may be constructed from a file descriptor or simply + * a string while the TCP medium usually from a socket. + * + * @hide + */ +public interface ObexTransport { + + void create() throws IOException; + + void listen() throws IOException; + + void close() throws IOException; + + void connect() throws IOException; + + void disconnect() throws IOException; + + InputStream openInputStream() throws IOException; + + OutputStream openOutputStream() throws IOException; + + DataInputStream openDataInputStream() throws IOException; + + DataOutputStream openDataOutputStream() throws IOException; + +} diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java new file mode 100644 index 0000000..5a8bcec --- /dev/null +++ b/obex/javax/obex/Operation.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The <code>Operation</code> interface provides ways to manipulate a single + * OBEX PUT or GET operation. The implementation of this interface sends + * OBEX packets as they are built. If during the operation the peer in the + * operation ends the operation, an <code>IOException</code> is thrown on + * the next read from the input stream, write to the output stream, or call to + * <code>sendHeaders()</code>. + * <P> + * <STRONG>Definition of methods inherited from <code>ContentConnection</code></STRONG> + * <P> + * <code>getEncoding()</code> will always return <code>null</code>. + * <BR><code>getLength()</code> will return the length specified by the OBEX Length + * header or -1 if the OBEX Length header was not included. + * <BR><code>getType()</code> will return the value specified in the OBEX Type + * header or <code>null</code> if the OBEX Type header was not included.<BR> + * <P> + * <STRONG>How Headers are Handled</STRONG> + * <P> + * As headers are received, they may be retrieved through the + * <code>getReceivedHeaders()</code> method. If new headers are set during the + * operation, the new headers will be sent during the next packet exchange. + * <P> + * <STRONG>PUT example</STRONG> + * <P> + * <PRE> + * void putObjectViaOBEX(ClientSession conn, HeaderSet head, byte[] obj) + * throws IOException { + * + * // Include the length header + * head.setHeader(head.LENGTH, new Long(obj.length)); + * + * // Initiate the PUT request + * Operation op = conn.put(head); + * + * // Open the output stream to put the object to it + * DataOutputStream out = op.openDataOutputStream(); + * + * // Send the object to the server + * out.write(obj); + * + * // End the transaction + * out.close(); + * op.close(); + * } + * </PRE> + * <P> + * <STRONG>GET example</STRONG> + * <P> + * <PRE> + * byte[] getObjectViaOBEX(ClientSession conn, HeaderSet head) throws IOException { + * + * // Send the initial GET request to the server + * Operation op = conn.get(head); + * + * // Retrieve the length of the object being sent back + * int length = op.getLength(); + * + * // Create space for the object + * byte[] obj = new byte[length]; + * + * // Get the object from the input stream + * DataInputStream in = trans.openDataInputStream(); + * in.read(obj); + * + * // End the transaction + * in.close(); + * op.close(); + * + * return obj; + * } + * </PRE> + * <H3>Client PUT Operation Flow</H3> + * For PUT operations, a call to <code>close()</code> the <code>OutputStream</code> + * returned from <code>openOutputStream()</code> or <code>openDataOutputStream()</code> + * will signal that the request is done. (In OBEX terms, the End-Of-Body header should + * be sent and the final bit in the request will be set.) At this point, the + * reply from the server may begin to be processed. A call to + * <code>getResponseCode()</code> will do an implicit close on the + * <code>OutputStream</code> and therefore signal that the request is done. + * <H3>Client GET Operation Flow</H3> + * For GET operation, a call to <code>openInputStream()</code> or + * <code>openDataInputStream()</code> will signal that the request is done. (In OBEX + * terms, the final bit in the request will be set.) A call to + * <code>getResponseCode()</code> will cause an implicit close on the + * <code>InputStream</code>. No further data may be read at this point. + * + * @hide + */ +public interface Operation { + + /** + * Sends an ABORT message to the server. By calling this method, the + * corresponding input and output streams will be closed along with this + * object. No headers are sent in the abort request. This will end the + * operation since <code>close()</code> will be called by this method. + * + * @throws IOException if the transaction has already ended or if an + * OBEX server calls this method + */ + public void abort() throws IOException; + + /** + * Returns the headers that have been received during the operation. + * Modifying the object returned has no effect on the headers that are + * sent or retrieved. + * + * @return the headers received during this <code>Operation</code> + * + * @throws IOException if this <code>Operation</code> has been closed + */ + public HeaderSet getReceivedHeaders() throws IOException; + + /** + * Specifies the headers that should be sent in the next OBEX message that + * is sent. + * + * @param headers the headers to send in the next message + * + * @throws IOException if this <code>Operation</code> has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @throws IllegalArgumentException if <code>headers</code> was not created + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> or + * <code>ClientSession.createHeaderSet()</code> + * + * @throws NullPointerException if <code>headers</code> if <code>null</code> + */ + public void sendHeaders(HeaderSet headers) throws IOException; + + /** + * Returns the response code received from the server. Response codes + * are defined in the <code>ResponseCodes</code> class. + * + * @see ResponseCodes + * + * @return the response code retrieved from the server + * + * @throws IOException if an error occurred in the transport layer during + * the transaction; if this object was created by an OBEX server + */ + public int getResponseCode() throws IOException; + + public String getEncoding(); + + public long getLength(); + + public String getType(); + + public InputStream openInputStream() throws IOException; + + public DataInputStream openDataInputStream() throws IOException; + + public OutputStream openOutputStream() throws IOException; + + public DataOutputStream openDataOutputStream() throws IOException; + + public void close() throws IOException; + + public int getMaxPacketSize(); +} diff --git a/obex/javax/obex/PasswordAuthentication.java b/obex/javax/obex/PasswordAuthentication.java new file mode 100644 index 0000000..49833fe --- /dev/null +++ b/obex/javax/obex/PasswordAuthentication.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +/** + * This class holds user name and password combinations. + * + * @hide + */ +public class PasswordAuthentication { + + private byte[] userName; + + private byte[] password; + + /** + * Creates a new <code>PasswordAuthentication</code> with the user name + * and password provided. + * + * @param userName the user name to include; this may be <code>null</code> + * + * @param password the password to include in the response + * + * @exception NullPointerException if <code>password</code> is + * <code>null</code> + */ + public PasswordAuthentication(byte[] userName, byte[] password) { + if (userName != null) { + this.userName = new byte[userName.length]; + System.arraycopy(userName, 0, this.userName, 0, userName.length); + } + + this.password = new byte[password.length]; + System.arraycopy(password, 0, this.password, 0, password.length); + } + + /** + * Retrieves the user name that was specified in the constructor. + * The user name may be <code>null</code>. + * + * @return the user name + */ + public byte[] getUserName() { + return this.userName; + } + + /** + * Retrieves the password. + * + * @return the password + */ + public byte[] getPassword() { + return this.password; + } +} diff --git a/obex/javax/obex/PrivateInputStream.java b/obex/javax/obex/PrivateInputStream.java new file mode 100644 index 0000000..9602649 --- /dev/null +++ b/obex/javax/obex/PrivateInputStream.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.InputStream; +import java.io.IOException; + +/** + * This object provides an input stream to the Operation objects used in this + * package. + * + * TODO: Include the other read() methods defined in InputStream. + * + * @hide + */ +public class PrivateInputStream extends InputStream { + + private BaseStream parent; + + private byte[] data; + + private int index; + + private boolean isOpen; + + public PrivateInputStream() { + + } + + /** + * Creates an input stream for the <code>Operation</code> to read from + * + * @param p the connection this input stream is for + */ + public PrivateInputStream(BaseStream p) { + parent = p; + data = new byte[0]; + index = 0; + isOpen = true; + } + + /** + * Returns the number of bytes that can be read (or skipped over) from this + * input stream without blocking by the next caller of a method for this + * input stream. The next caller might be the same thread or or another + * thread. + * + * @return the number of bytes that can be read from this input stream + * without blocking + * + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized int available() throws IOException { + ensureOpen(); + return data.length - index; + } + + /** + * Reads the next byte of data from the input stream. The value byte is + * returned as an int in the range 0 to 255. If no byte is available + * because the end of the stream has been reached, the value -1 is + * returned. This method blocks until input data is available, the end of + * the stream is detected, or an exception is thrown. + * + * @return the byte read from the input stream or -1 if it reaches the end + * of stream + * + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized int read() throws IOException { + ensureOpen(); + while (data.length == index) { + if (!parent.continueOperation(true, true)) { + return -1; + } + } + return (data[index++] & 0xFF); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public synchronized int read(byte[] b, int offset, int length) throws IOException { + + if (b == null) { + throw new NullPointerException("buffer is null"); + } + if ((offset | length) < 0 || length > b.length - offset) { + throw new ArrayIndexOutOfBoundsException("index outof bound"); + } + ensureOpen(); + + int currentDataLength = data.length - index; + int remainReadLength = length; + int offset1 = offset; + int result = 0; + + while (currentDataLength <= remainReadLength) { + System.arraycopy(data, index, b, offset1, currentDataLength); + index += currentDataLength; + offset1 += currentDataLength; + result += currentDataLength; + remainReadLength -= currentDataLength; + + if (!parent.continueOperation(true, true)) { + return result == 0 ? -1 : result; + } + currentDataLength = data.length - index; + } + if (remainReadLength > 0) { + System.arraycopy(data, index, b, offset1, remainReadLength); + index += remainReadLength; + result += remainReadLength; + } + return result; + } + + /** + * Allows the <code>OperationImpl</code> thread to add body data to the + * input stream. + * + * @param body the data to add to the stream + * + * @param start the start of the body to array to copy + */ + public synchronized void writeBytes(byte[] body, int start) { + + int length = (body.length - start) + (data.length - index); + byte[] temp = new byte[length]; + + System.arraycopy(data, index, temp, 0, data.length - index); + System.arraycopy(body, start, temp, data.length - index, body.length - start); + + data = temp; + index = 0; + notifyAll(); + } + + /** + * Verifies that this stream is open + * + * @throws IOException if the stream is not open + */ + private void ensureOpen() throws IOException { + parent.ensureOpen(); + if (!isOpen) { + throw new IOException("Input stream is closed"); + } + } + + /** + * Closes the input stream. If the input stream is already closed, do + * nothing. + * + * @throws IOException this will never happen + */ + @Override + public void close() throws IOException { + isOpen = false; + parent.streamClosed(true); + } +} diff --git a/obex/javax/obex/PrivateOutputStream.java b/obex/javax/obex/PrivateOutputStream.java new file mode 100644 index 0000000..75220d6 --- /dev/null +++ b/obex/javax/obex/PrivateOutputStream.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; + +/** + * This object provides an output stream to the Operation objects used in this + * package. + * + * @hide + */ +class PrivateOutputStream extends OutputStream { + + private BaseStream parent; + + private ByteArrayOutputStream output; + + private boolean isClosed; + + private int maxPacketSize; + + /** + * Creates an empty <code>PrivateOutputStream</code> to write to. + * + * @param p the connection that this stream runs over + */ + public PrivateOutputStream(BaseStream p, int maxSize) { + parent = p; + output = new ByteArrayOutputStream(); + maxPacketSize = maxSize; + } + + /** + * Determines how many bytes have been written to the output stream. + * + * @return the number of bytes written to the output stream + */ + protected int size() { + return output.size(); + } + + /** + * Writes the specified byte to this output stream. The general contract + * for write is that one byte is written to the output stream. The byte to + * be written is the eight low-order bits of the argument b. The 24 + * high-order bits of b are ignored. + * + * @param b the byte to write + * + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized void write(int b) throws IOException { + ensureOpen(); + parent.ensureNotDone(); + output.write(b); + if (output.size() == maxPacketSize) { + parent.continueOperation(true, false); + } + } + + @Override + public void write(byte[] buffer) throws IOException { + write(buffer, 0, buffer.length); + } + + @Override + public synchronized void write(byte[] buffer, int offset, int count) throws IOException { + int offset1 = offset; + int remainLength = count; + + if (buffer == null) { + throw new NullPointerException("buffer is null"); + } + if ((offset | count) < 0 || count > buffer.length - offset) { + throw new IndexOutOfBoundsException("index outof bound"); + } + + ensureOpen(); + parent.ensureNotDone(); + if (count < maxPacketSize) { + output.write(buffer, offset, count); + } else { + while (remainLength >= maxPacketSize) { + output.write(buffer, offset1, maxPacketSize); + offset1 += maxPacketSize; + remainLength = count - offset1; + parent.continueOperation(true, false); + } + if (remainLength > 0) { + output.write(buffer, offset1, remainLength); + } + } + } + + /** + * Reads the bytes that have been written to this stream. + * + * @return the byte array that is written + */ + protected synchronized byte[] readBytes() { + if (output.size() > 0) { + byte[] result = output.toByteArray(); + output.reset(); + return result; + } else { + return null; + } + } + + /** + * Reads the bytes that have been written to this stream. + * + * @param size the size of the array to return + * + * @return the byte array that is written + */ + protected synchronized byte[] readBytes(int size) { + if (output.size() > 0) { + byte[] temp = output.toByteArray(); + output.reset(); + byte[] result = new byte[size]; + System.arraycopy(temp, 0, result, 0, size); + if (temp.length != size) { + output.write(temp, size, temp.length - size); + } + return result; + } else { + return null; + } + } + + /** + * Verifies that this stream is open + * + * @throws IOException if the stream is not open + */ + private void ensureOpen() throws IOException { + parent.ensureOpen(); + if (isClosed) { + throw new IOException("Output stream is closed"); + } + } + + /** + * Closes the output stream. If the input stream is already closed, do + * nothing. + * + * @throws IOException this will never happen + */ + @Override + public void close() throws IOException { + isClosed = true; + parent.streamClosed(false); + } + + /** + * Determines if the connection is closed + * + * @return <code>true</code> if the connection is closed; + * <code>false</code> if the connection is open + */ + protected boolean isClosed() { + return isClosed; + } +} diff --git a/obex/javax/obex/ResponseCodes.java b/obex/javax/obex/ResponseCodes.java new file mode 100644 index 0000000..2d7627b --- /dev/null +++ b/obex/javax/obex/ResponseCodes.java @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +/** + * The <code>ResponseCodes</code> class contains the list of valid + * response codes a server may send to a client. + * <P> + * <STRONG>IMPORTANT NOTE</STRONG> + * <P> + * It is important to note that these values are different then those defined + * in <code>javax.microedition.io.HttpConnection</code>. The values in this + * interface represent the values defined in the IrOBEX specification. The + * values in <code>javax.microedition.io.HttpConnection</code> represent values + * defined in the HTTP specification. + * <P> + * <code>OBEX_DATABASE_FULL</code> and <code>OBEX_DATABASE_LOCKED</code> require + * further description since they are not defined in HTTP. The server will send + * an <code>OBEX_DATABASE_FULL</code> message when the client requests that + * something be placed into a database but the database is full (cannot take + * more data). <code>OBEX_DATABASE_LOCKED</code> will be returned when the + * client wishes to access a database, database table, or database record that + * has been locked. + * + * @hide + */ +public class ResponseCodes { + + /** + * Defines the OBEX SUCCESS response code. + * <P> + * The value of <code>OBEX_HTTP_OK</code> is 0xA0 (160). + */ + public static final int OBEX_HTTP_OK = 0xA0; + + /** + * Defines the OBEX CREATED response code. + * <P> + * The value of <code>OBEX_HTTP_CREATED</code> is 0xA1 (161). + */ + public static final int OBEX_HTTP_CREATED = 0xA1; + + /** + * Defines the OBEX ACCEPTED response code. + * <P> + * The value of <code>OBEX_HTTP_ACCEPTED</code> is 0xA2 (162). + */ + public static final int OBEX_HTTP_ACCEPTED = 0xA2; + + /** + * Defines the OBEX NON-AUTHORITATIVE INFORMATION response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_AUTHORITATIVE</code> is 0xA3 (163). + */ + public static final int OBEX_HTTP_NOT_AUTHORITATIVE = 0xA3; + + /** + * Defines the OBEX NO CONTENT response code. + * <P> + * The value of <code>OBEX_HTTP_NO_CONTENT</code> is 0xA4 (164). + */ + public static final int OBEX_HTTP_NO_CONTENT = 0xA4; + + /** + * Defines the OBEX RESET CONTENT response code. + * <P> + * The value of <code>OBEX_HTTP_RESET</code> is 0xA5 (165). + */ + public static final int OBEX_HTTP_RESET = 0xA5; + + /** + * Defines the OBEX PARTIAL CONTENT response code. + * <P> + * The value of <code>OBEX_HTTP_PARTIAL</code> is 0xA6 (166). + */ + public static final int OBEX_HTTP_PARTIAL = 0xA6; + + /** + * Defines the OBEX MULTIPLE_CHOICES response code. + * <P> + * The value of <code>OBEX_HTTP_MULT_CHOICE</code> is 0xB0 (176). + */ + public static final int OBEX_HTTP_MULT_CHOICE = 0xB0; + + /** + * Defines the OBEX MOVED PERMANENTLY response code. + * <P> + * The value of <code>OBEX_HTTP_MOVED_PERM</code> is 0xB1 (177). + */ + public static final int OBEX_HTTP_MOVED_PERM = 0xB1; + + /** + * Defines the OBEX MOVED TEMPORARILY response code. + * <P> + * The value of <code>OBEX_HTTP_MOVED_TEMP</code> is 0xB2 (178). + */ + public static final int OBEX_HTTP_MOVED_TEMP = 0xB2; + + /** + * Defines the OBEX SEE OTHER response code. + * <P> + * The value of <code>OBEX_HTTP_SEE_OTHER</code> is 0xB3 (179). + */ + public static final int OBEX_HTTP_SEE_OTHER = 0xB3; + + /** + * Defines the OBEX NOT MODIFIED response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_MODIFIED</code> is 0xB4 (180). + */ + public static final int OBEX_HTTP_NOT_MODIFIED = 0xB4; + + /** + * Defines the OBEX USE PROXY response code. + * <P> + * The value of <code>OBEX_HTTP_USE_PROXY</code> is 0xB5 (181). + */ + public static final int OBEX_HTTP_USE_PROXY = 0xB5; + + /** + * Defines the OBEX BAD REQUEST response code. + * <P> + * The value of <code>OBEX_HTTP_BAD_REQUEST</code> is 0xC0 (192). + */ + public static final int OBEX_HTTP_BAD_REQUEST = 0xC0; + + /** + * Defines the OBEX UNAUTHORIZED response code. + * <P> + * The value of <code>OBEX_HTTP_UNAUTHORIZED</code> is 0xC1 (193). + */ + public static final int OBEX_HTTP_UNAUTHORIZED = 0xC1; + + /** + * Defines the OBEX PAYMENT REQUIRED response code. + * <P> + * The value of <code>OBEX_HTTP_PAYMENT_REQUIRED</code> is 0xC2 (194). + */ + public static final int OBEX_HTTP_PAYMENT_REQUIRED = 0xC2; + + /** + * Defines the OBEX FORBIDDEN response code. + * <P> + * The value of <code>OBEX_HTTP_FORBIDDEN</code> is 0xC3 (195). + */ + public static final int OBEX_HTTP_FORBIDDEN = 0xC3; + + /** + * Defines the OBEX NOT FOUND response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_FOUND</code> is 0xC4 (196). + */ + public static final int OBEX_HTTP_NOT_FOUND = 0xC4; + + /** + * Defines the OBEX METHOD NOT ALLOWED response code. + * <P> + * The value of <code>OBEX_HTTP_BAD_METHOD</code> is 0xC5 (197). + */ + public static final int OBEX_HTTP_BAD_METHOD = 0xC5; + + /** + * Defines the OBEX NOT ACCEPTABLE response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_ACCEPTABLE</code> is 0xC6 (198). + */ + public static final int OBEX_HTTP_NOT_ACCEPTABLE = 0xC6; + + /** + * Defines the OBEX PROXY AUTHENTICATION REQUIRED response code. + * <P> + * The value of <code>OBEX_HTTP_PROXY_AUTH</code> is 0xC7 (199). + */ + public static final int OBEX_HTTP_PROXY_AUTH = 0xC7; + + /** + * Defines the OBEX REQUEST TIME OUT response code. + * <P> + * The value of <code>OBEX_HTTP_TIMEOUT</code> is 0xC8 (200). + */ + public static final int OBEX_HTTP_TIMEOUT = 0xC8; + + /** + * Defines the OBEX METHOD CONFLICT response code. + * <P> + * The value of <code>OBEX_HTTP_CONFLICT</code> is 0xC9 (201). + */ + public static final int OBEX_HTTP_CONFLICT = 0xC9; + + /** + * Defines the OBEX METHOD GONE response code. + * <P> + * The value of <code>OBEX_HTTP_GONE</code> is 0xCA (202). + */ + public static final int OBEX_HTTP_GONE = 0xCA; + + /** + * Defines the OBEX METHOD LENGTH REQUIRED response code. + * <P> + * The value of <code>OBEX_HTTP_LENGTH_REQUIRED</code> is 0xCB (203). + */ + public static final int OBEX_HTTP_LENGTH_REQUIRED = 0xCB; + + /** + * Defines the OBEX PRECONDITION FAILED response code. + * <P> + * The value of <code>OBEX_HTTP_PRECON_FAILED</code> is 0xCC (204). + */ + public static final int OBEX_HTTP_PRECON_FAILED = 0xCC; + + /** + * Defines the OBEX REQUESTED ENTITY TOO LARGE response code. + * <P> + * The value of <code>OBEX_HTTP_ENTITY_TOO_LARGE</code> is 0xCD (205). + */ + public static final int OBEX_HTTP_ENTITY_TOO_LARGE = 0xCD; + + /** + * Defines the OBEX REQUESTED URL TOO LARGE response code. + * <P> + * The value of <code>OBEX_HTTP_REQ_TOO_LARGE</code> is 0xCE (206). + */ + public static final int OBEX_HTTP_REQ_TOO_LARGE = 0xCE; + + /** + * Defines the OBEX UNSUPPORTED MEDIA TYPE response code. + * <P> + * The value of <code>OBEX_HTTP_UNSUPPORTED_TYPE</code> is 0xCF (207). + */ + public static final int OBEX_HTTP_UNSUPPORTED_TYPE = 0xCF; + + /** + * Defines the OBEX INTERNAL SERVER ERROR response code. + * <P> + * The value of <code>OBEX_HTTP_INTERNAL_ERROR</code> is 0xD0 (208). + */ + public static final int OBEX_HTTP_INTERNAL_ERROR = 0xD0; + + /** + * Defines the OBEX NOT IMPLEMENTED response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_IMPLEMENTED</code> is 0xD1 (209). + */ + public static final int OBEX_HTTP_NOT_IMPLEMENTED = 0xD1; + + /** + * Defines the OBEX BAD GATEWAY response code. + * <P> + * The value of <code>OBEX_HTTP_BAD_GATEWAY</code> is 0xD2 (210). + */ + public static final int OBEX_HTTP_BAD_GATEWAY = 0xD2; + + /** + * Defines the OBEX SERVICE UNAVAILABLE response code. + * <P> + * The value of <code>OBEX_HTTP_UNAVAILABLE</code> is 0xD3 (211). + */ + public static final int OBEX_HTTP_UNAVAILABLE = 0xD3; + + /** + * Defines the OBEX GATEWAY TIMEOUT response code. + * <P> + * The value of <code>OBEX_HTTP_GATEWAY_TIMEOUT</code> is 0xD4 (212). + */ + public static final int OBEX_HTTP_GATEWAY_TIMEOUT = 0xD4; + + /** + * Defines the OBEX HTTP VERSION NOT SUPPORTED response code. + * <P> + * The value of <code>OBEX_HTTP_VERSION</code> is 0xD5 (213). + */ + public static final int OBEX_HTTP_VERSION = 0xD5; + + /** + * Defines the OBEX DATABASE FULL response code. + * <P> + * The value of <code>OBEX_DATABASE_FULL</code> is 0xE0 (224). + */ + public static final int OBEX_DATABASE_FULL = 0xE0; + + /** + * Defines the OBEX DATABASE LOCKED response code. + * <P> + * The value of <code>OBEX_DATABASE_LOCKED</code> is 0xE1 (225). + */ + public static final int OBEX_DATABASE_LOCKED = 0xE1; + + /** + * Constructor does nothing. + */ + private ResponseCodes() {} +} diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java new file mode 100644 index 0000000..5dcbb93 --- /dev/null +++ b/obex/javax/obex/ServerOperation.java @@ -0,0 +1,771 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.IOException; +import java.io.InputStream; +import java.io.DataInputStream; +import java.io.OutputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; + +/** + * This class implements the Operation interface for server side connections. + * <P> + * <STRONG>Request Codes</STRONG> + * There are four different request codes that are in this class. 0x02 is a + * PUT request that signals that the request is not complete and requires an + * additional OBEX packet. 0x82 is a PUT request that says that request is + * complete. In this case, the server can begin sending the response. The + * 0x03 is a GET request that signals that the request is not finished. When + * the server receives a 0x83, the client is signalling the server that it is + * done with its request. + * + * TODO: Extend the ClientOperation and reuse the methods defined + * TODO: in that class. + * + * @hide + */ +public class ServerOperation implements Operation, BaseStream { + + private InputStream socketInput; + + private ServerSession parent; + + private int maxPacketLength; + + private int responseSize; + + private boolean isClosed; + + boolean finalBitSet; + + // This variable defines when the end of body + // header has been received. When this header + // is received, no further body data will be + // received from the client + private boolean endOfBody; + + private boolean isGet; + + boolean isAborted; + + HeaderSet requestHeaders; + + HeaderSet replyHeaders; + + PrivateInputStream privateInput; + + private PrivateOutputStream privateOutput; + + private String exceptionString; + + private ServerRequestHandler listener; + + private boolean outputStreamOpened; + + private boolean requestFinished; + + private static final int BASE_PACKET_LENGTH = 3; + + private boolean isHasBody; + + /** + * Creates new PutServerOperation + * + * @param p the parent that created this object + * + * @param in the input stream to read from + * + * @param out the output stream to write to + * + * @param request the initial request that was received from the client + * + * @param maxSize the max packet size that the client will accept + * + * @param listen the listener that is responding to the request + * + * @throws IOException if an IO error occurs + */ + public ServerOperation(ServerSession p, InputStream in, int request, int maxSize, + ServerRequestHandler listen) throws IOException { + + isAborted = false; + parent = p; + socketInput = in; + maxPacketLength = maxSize; + isClosed = false; + requestHeaders = new HeaderSet(); + replyHeaders = new HeaderSet(); + privateInput = new PrivateInputStream(this); + endOfBody = false; + responseSize = 3; + listener = listen; + requestFinished = false; + outputStreamOpened = false; + isHasBody = false; + int bytesReceived; + + /* + * Determine if this is a PUT request + */ + if ((request == 0x02) || (request == 0x82)) { + /* + * It is a PUT request. + */ + isGet = false; + } else { + /* + * It is a GET request. + */ + isGet = true; + } + + /* + * Determine if the final bit is set + */ + if ((request & 0x80) == 0) { + finalBitSet = false; + } else { + finalBitSet = true; + requestFinished = true; + } + + int length = in.read(); + length = (length << 8) + in.read(); + + /* + * Determine if the packet length is larger than this device can receive + */ + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); + throw new IOException("Packet received was too large"); + } + + /* + * Determine if any headers were sent in the initial request + */ + if (length > 3) { + byte[] data = new byte[length - 3]; + bytesReceived = in.read(data); + + while (bytesReceived != data.length) { + bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived); + } + + byte[] body = ObexHelper.updateHeaderSet(requestHeaders, data); + + if (body != null) { + isHasBody = true; + } + + if (requestHeaders.connectionID != null) { + listener.setConnectionID(ObexHelper.convertToLong(requestHeaders.connectionID)); + } else { + listener.setConnectionID(0); + } + + if (requestHeaders.authResp != null) { + if (!parent.handleAuthResp(requestHeaders.authResp)) { + exceptionString = "Authentication Failed"; + parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + isClosed = true; + requestHeaders.authResp = null; + return; + } + } + + if (requestHeaders.authChall != null) { + parent.handleAuthChall(requestHeaders); + // send the authResp to the client + replyHeaders.authResp = new byte[requestHeaders.authResp.length]; + System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0, + replyHeaders.authResp.length); + requestHeaders.authResp = null; + requestHeaders.authChall = null; + + } + + if (body != null) { + /* + * 0x49 is the end of body header. This signifies that no more + * body data will be sent from the client + */ + if (body[0] == 0x49) { + endOfBody = true; + } + //privateInput.writeBytes(body, body.length); + //byte [] body_tmp = new byte[body.length-1]; + //System.arraycopy(body,1,body_tmp,0,body.length-1); + //privateInput.writeBytes(body_tmp, body.length-1); + privateInput.writeBytes(body, 1); + } else { + while ((!isGet) && (!finalBitSet)) { + sendReply(ObexHelper.OBEX_HTTP_CONTINUE); + if (privateInput.available() > 0) { + break; + } + } + }// if (body != null) + + }// if (length > 3) + + while ((!isGet) && (!finalBitSet) && (privateInput.available() == 0)) { + sendReply(ObexHelper.OBEX_HTTP_CONTINUE); + if (privateInput.available() > 0) { + break; + } + } + + // wait for get request finished !!!! + while (isGet && !finalBitSet) { + sendReply(ObexHelper.OBEX_HTTP_CONTINUE); + } + if (finalBitSet && isGet) { + requestFinished = true; + } + } + + public synchronized boolean isValidBody() { + return isHasBody; + } + + /** + * Determines if the operation should continue or should wait. If it + * should continue, this method will continue the operation. + * + * @param sendEmpty if <code>true</code> then this will continue the + * operation even if no headers will be sent; if <code>false</code> then + * this method will only continue the operation if there are headers to + * send + * @param isStream if<code>true</code> the stream is input stream or + * is outputstream + * @return <code>true</code> if the operation was completed; + * <code>false</code> if no operation took place + */ + public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) + throws IOException { + if (!isGet) { + if (!finalBitSet) { + if (sendEmpty) { + sendReply(ObexHelper.OBEX_HTTP_CONTINUE); + return true; + } else { + if ((responseSize > 3) || (privateOutput.size() > 0)) { + sendReply(ObexHelper.OBEX_HTTP_CONTINUE); + return true; + } else { + return false; + } + } + } else { + return false; + } + } else { + sendReply(ObexHelper.OBEX_HTTP_CONTINUE); + return true; + } + } + + /** + * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it + * will wait for a response from the client before ending. + * + * @param type the response code to send back to the client + * + * @return <code>true</code> if the final bit was not set on the reply; + * <code>false</code> if no reply was received because the operation ended, + * an abort was received, or the final bit was set in the reply + * + * @throws IOException if an IO error occurs + */ + protected synchronized boolean sendReply(int type) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int bytesReceived; + + long id = listener.getConnectionID(); + if (id == -1) { + replyHeaders.connectionID = null; + } else { + replyHeaders.connectionID = ObexHelper.convertToByteArray(id); + } + + byte[] headerArray = ObexHelper.createHeader(replyHeaders, true); + int bodyLength = -1; + int orginalBodyLength = -1; + + if (privateOutput != null) { + bodyLength = privateOutput.size(); + orginalBodyLength = bodyLength; + } + + if ((BASE_PACKET_LENGTH + headerArray.length) > maxPacketLength) { + + int end = 0; + int start = 0; + + while (end != headerArray.length) { + end = ObexHelper.findHeaderEnd(headerArray, start, maxPacketLength + - BASE_PACKET_LENGTH); + if (end == -1) { + + isClosed = true; + + if (privateInput != null) { + privateInput.close(); + } + + if (privateOutput != null) { + privateOutput.close(); + } + parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + throw new IOException("OBEX Packet exceeds max packet size"); + } + byte[] sendHeader = new byte[end - start]; + System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); + + parent.sendResponse(type, sendHeader); + start = end; + } + + if (bodyLength > 0) { + return true; + } else { + return false; + } + + } else { + out.write(headerArray); + } + + /* + * Determine if there is space to add a body reply. First, we need to + * verify that the client is finished sending the request. Next, there + * needs to be enough space to send the headers already defined along + * with the reply header (3 bytes) and the body header identifier + * (3 bytes). + */ + + /* if ((finalBitSet) && + ((bodyLength + 6 + headerArray.length) > maxPacketLength)) { + + exceptionString = "Header larger then can be sent in a packet"; + isClosed = true; + privateInput.close(); + if (privateOutput != null) { + privateOutput.close(); + } + parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, + null); + throw new IOException("OBEX Packet exceeds max packet size"); + } + */ + + if ((finalBitSet) || (headerArray.length < (maxPacketLength - 20))) { + if (bodyLength > 0) { + /* + * Determine if I can send the whole body or just part of + * the body. Remember that there is the 3 bytes for the + * response message and 3 bytes for the header ID and length + */ + if (bodyLength > (maxPacketLength - headerArray.length - 6)) { + bodyLength = maxPacketLength - headerArray.length - 6; + } + + byte[] body = privateOutput.readBytes(bodyLength); + + /* + * Since this is a put request if the final bit is set or + * the output stream is closed we need to send the 0x49 + * (End of Body) otherwise, we need to send 0x48 (Body) + */ + if ((finalBitSet) || (privateOutput.isClosed())) { + out.write(0x49); + } else { + out.write(0x48); + } + + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + out.write(body); + } + } + + if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { + out.write(0x49); + orginalBodyLength = 3; + out.write((byte)(orginalBodyLength >> 8)); + out.write((byte)orginalBodyLength); + + } + + responseSize = 3; + parent.sendResponse(type, out.toByteArray()); + + if (type == ObexHelper.OBEX_HTTP_CONTINUE) { + int headerID = socketInput.read(); + int length = socketInput.read(); + length = (length << 8) + socketInput.read(); + if ((headerID != 0x02) && (headerID != 0x82) && (headerID != 0x03) + && (headerID != 0x83)) { + + if (length > 3) { + byte[] temp = new byte[length]; + bytesReceived = socketInput.read(temp); + + while (bytesReceived != length) { + bytesReceived += socketInput.read(temp, bytesReceived, length + - bytesReceived); + } + } + + /* + * Determine if an ABORT was sent as the reply + */ + if (headerID == 0xFF) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); + isClosed = true; + isAborted = true; + exceptionString = "Abort Received"; + throw new IOException("Abort Received"); + } else { + parent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); + isClosed = true; + exceptionString = "Bad Request Received"; + throw new IOException("Bad Request Received"); + } + } else { + + if ((headerID == 0x82) || (headerID == 0x83)) { + finalBitSet = true; + } + + /* + * Determine if the packet length is larger then this device can receive + */ + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); + throw new IOException("Packet received was too large"); + } + + /* + * Determine if any headers were sent in the initial request + */ + if (length > 3) { + byte[] data = new byte[length - 3]; + bytesReceived = socketInput.read(data); + + while (bytesReceived != data.length) { + bytesReceived += socketInput.read(data, bytesReceived, data.length + - bytesReceived); + } + byte[] body = ObexHelper.updateHeaderSet(requestHeaders, data); + if (body != null) { + isHasBody = true; + } + if (requestHeaders.connectionID != null) { + listener.setConnectionID(ObexHelper + .convertToLong(requestHeaders.connectionID)); + } else { + listener.setConnectionID(1); + } + + if (requestHeaders.authResp != null) { + if (!parent.handleAuthResp(requestHeaders.authResp)) { + exceptionString = "Authentication Failed"; + parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + isClosed = true; + requestHeaders.authResp = null; + return false; + } + requestHeaders.authResp = null; + } + + if (requestHeaders.authChall != null) { + parent.handleAuthChall(requestHeaders); + // send the auhtResp to the client + replyHeaders.authResp = new byte[requestHeaders.authResp.length]; + System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0, + replyHeaders.authResp.length); + requestHeaders.authResp = null; + requestHeaders.authChall = null; + } + + if (body != null) { + if (body[0] == 0x49) { + endOfBody = true; + } + + /*byte [] body_tmp = new byte[body.length-1]; + System.arraycopy(body,1,body_tmp,0,body.length-1); + privateInput.writeBytes(body_tmp, body.length-1);*/ + privateInput.writeBytes(body, 1); + + } + } + } + return true; + } else { + return false; + } + } + + /** + * Sends an ABORT message to the server. By calling this method, the + * corresponding input and output streams will be closed along with this + * object. + * + * @throws IOException if the transaction has already ended or if an + * OBEX server called this method + */ + public void abort() throws IOException { + throw new IOException("Called from a server"); + } + + /** + * Returns the headers that have been received during the operation. + * Modifying the object returned has no effect on the headers that are + * sent or retrieved. + * + * @return the headers received during this <code>Operation</code> + * + * @throws IOException if this <code>Operation</code> has been closed + */ + public HeaderSet getReceivedHeaders() throws IOException { + ensureOpen(); + return requestHeaders; + } + + /** + * Specifies the headers that should be sent in the next OBEX message that + * is sent. + * + * @param headers the headers to send in the next message + * + * @throws IOException if this <code>Operation</code> has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @throws IllegalArgumentException if <code>headers</code> was not created + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> + */ + public void sendHeaders(HeaderSet headers) throws IOException { + ensureOpen(); + + if (headers == null) { + throw new NullPointerException("Headers may not be null"); + } + + int[] headerList = headers.getHeaderList(); + if (headerList != null) { + for (int i = 0; i < headerList.length; i++) { + replyHeaders.setHeader(headerList[i], headers.getHeader(headerList[i])); + } + + } + } + + /** + * Retrieves the response code retrieved from the server. Response codes + * are defined in the <code>ResponseCodes</code> interface. + * + * @return the response code retrieved from the server + * + * @throws IOException if an error occurred in the transport layer during + * the transaction; if this method is called on a <code>HeaderSet</code> + * object created by calling <code>createHeaderSet</code> in a + * <code>ClientSession</code> object; if this is called from a server + */ + public int getResponseCode() throws IOException { + throw new IOException("Called from a server"); + } + + /** + * Always returns <code>null</code> + * + * @return <code>null</code> + */ + public String getEncoding() { + return null; + } + + /** + * Returns the type of content that the resource connected to is providing. + * E.g. if the connection is via HTTP, then the value of the content-type + * header field is returned. + * + * @return the content type of the resource that the URL references, or + * <code>null</code> if not known + */ + public String getType() { + try { + return (String)requestHeaders.getHeader(HeaderSet.TYPE); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the length of the content which is being provided. E.g. if the + * connection is via HTTP, then the value of the content-length header + * field is returned. + * + * @return the content length of the resource that this connection's URL + * references, or -1 if the content length is not known + */ + public long getLength() { + try { + Long temp = (Long)requestHeaders.getHeader(HeaderSet.LENGTH); + + if (temp == null) { + return -1; + } else { + return temp.longValue(); + } + } catch (IOException e) { + return -1; + } + } + + public int getMaxPacketSize() { + return maxPacketLength - 6; + } + + /** + * Open and return an input stream for a connection. + * + * @return an input stream + * + * @throws IOException if an I/O error occurs + */ + public InputStream openInputStream() throws IOException { + ensureOpen(); + return privateInput; + } + + /** + * Open and return a data input stream for a connection. + * + * @return an input stream + * + * @throws IOException if an I/O error occurs + */ + public DataInputStream openDataInputStream() throws IOException { + return new DataInputStream(openInputStream()); + } + + /** + * Open and return an output stream for a connection. + * + * @return an output stream + * + * @throws IOException if an I/O error occurs + */ + public OutputStream openOutputStream() throws IOException { + ensureOpen(); + + if (outputStreamOpened) + throw new IOException("no more input streams available, stream already opened"); + + if (!requestFinished) + throw new IOException("no output streams available ,request not finished"); + + if (privateOutput == null) { + privateOutput = new PrivateOutputStream(this, maxPacketLength - 6); + } + outputStreamOpened = true; + return privateOutput; + } + + /** + * Open and return a data output stream for a connection. + * + * @return an output stream + * + * @throws IOException if an I/O error occurs + */ + public DataOutputStream openDataOutputStream() throws IOException { + return new DataOutputStream(openOutputStream()); + } + + /** + * Closes the connection and ends the transaction + * + * @throws IOException if the operation has already ended or is closed + */ + public void close() throws IOException { + ensureOpen(); + isClosed = true; + } + + /** + * Verifies that the connection is open and no exceptions should be thrown. + * + * @throws IOException if an exception needs to be thrown + */ + public void ensureOpen() throws IOException { + if (exceptionString != null) { + throw new IOException(exceptionString); + } + if (isClosed) { + throw new IOException("Operation has already ended"); + } + } + + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + * <P> + * Included to implement the BaseStream interface only. It does not do + * anything on the server side since the operation of the Operation object + * is not done until after the handler returns from its method. + * + * @throws IOException if the operation is completed + */ + public void ensureNotDone() throws IOException { + } + + /** + * Called when the output or input stream is closed. It does not do + * anything on the server side since the operation of the Operation object + * is not done until after the handler returns from its method. + * + * @param inStream <code>true</code> if the input stream is closed; + * <code>false</code> if the output stream is closed + * + * @throws IOException if an IO error occurs + */ + public void streamClosed(boolean inStream) throws IOException { + + } +} diff --git a/obex/javax/obex/ServerRequestHandler.java b/obex/javax/obex/ServerRequestHandler.java new file mode 100644 index 0000000..955e916 --- /dev/null +++ b/obex/javax/obex/ServerRequestHandler.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +/** + * The <code>ServerRequestHandler</code> class defines an event + * listener that will respond to OBEX requests made to the server. + * <P> + * The <code>onConnect()</code>, <code>onSetPath()</code>, <code>onDelete()</code>, + * <code>onGet()</code>, + * and <code>onPut()</code> methods may return any response code defined + * in the <code>ResponseCodes</code> class except for + * <code>OBEX_HTTP_CONTINUE</code>. If <code>OBEX_HTTP_CONTINUE</code> or + * a value not defined in the <code>ResponseCodes</code> class is returned, + * the server implementation will send an <code>OBEX_HTTP_INTERNAL_ERROR</code> + * response to the client. + * <P> + * <STRONG>Connection ID and Target Headers</STRONG> + * <P> + * According to the IrOBEX specification, a packet may not contain a Connection + * ID and Target header. Since the Connection ID header is managed by the + * implementation, it will not send a Connection ID header, if a Connection ID + * was specified, in a packet that has a Target header. In other words, if an + * application adds a Target header to a <code>HeaderSet</code> object used + * in an OBEX operation and a Connection ID was specified, no Connection ID + * will be sent in the packet containing the Target header. + * <P> + * <STRONG>CREATE-EMPTY Requests</STRONG> + * <P> + * A CREATE-EMPTY request allows clients to create empty objects on the server. + * When a CREATE-EMPTY request is received, the <code>onPut()</code> method + * will be called by the implementation. To differentiate between a normal + * PUT request and a CREATE-EMPTY request, an application must open the + * <code>InputStream</code> from the <code>Operation</code> object passed + * to the <code>onPut()</code> method. For a PUT request, the application + * will be able to read Body data from this <code>InputStream</code>. For + * a CREATE-EMPTY request, there will be no Body data to read. Therefore, + * a call to <code>InputStream.read()</code> will return -1. + * + * @hide + */ +public class ServerRequestHandler { + + private long connectionID; + + /** + * Creates a <code>ServerRequestHandler</code>. + */ + protected ServerRequestHandler() { + /* + * A connection ID of -1 implies there is no conenction ID + */ + connectionID = -1; + } + + /** + * Creates a <code>HeaderSet</code> object that may be used in put and get + * operations. + * + * @return the <code>HeaderSet</code> object to use in put and get operations + */ + public final HeaderSet createHeaderSet() { + return new HeaderSet(); + } + + /** + * Sets the connection ID header to include in the reply packets. + * + * @param id the connection ID to use; -1 if no connection ID should be + * sent + * + * @throws IllegalArgumentException if <code>id</code> is not in the + * range -1 to 2<sup>32</sup>-1 + */ + public void setConnectionID(long id) { + if ((id < -1) || (id > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Illegal Connection ID"); + } + connectionID = id; + } + + /** + * Retrieves the connection ID that is being used in the present connection. + * This method will return -1 if no connection ID is being used. + * + * @return the connection id being used or -1 if no connection ID is being + * used + */ + public long getConnectionID() { + return connectionID; + } + + /** + * Called when a CONNECT request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onConnect()</code> will always return an + * <code>OBEX_HTTP_OK</code> response code. + * <P> + * The headers received in the request can be retrieved from the + * <code>request</code> argument. The headers that should be sent + * in the reply must be specified in the <code>reply</code> argument. + * + * @param request contains the headers sent by the client; + * <code>request</code> will never be <code>null</code> + * + * @param reply the headers that should be sent in the reply; + * <code>reply</code> will never be <code>null</code> + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onConnect(HeaderSet request, HeaderSet reply) { + return ResponseCodes.OBEX_HTTP_OK; + } + + /** + * Called when a DISCONNECT request is received. + * <P> + * The headers received in the request can be retrieved from the + * <code>request</code> argument. The headers that should be sent + * in the reply must be specified in the <code>reply</code> argument. + * + * @param request contains the headers sent by the client; + * <code>request</code> will never be <code>null</code> + * + * @param reply the headers that should be sent in the reply; + * <code>reply</code> will never be <code>null</code> + */ + public void onDisconnect(HeaderSet request, HeaderSet reply) { + } + + /** + * Called when a SETPATH request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onSetPath()</code> will always return an + * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. + * <P> + * The headers received in the request can be retrieved from the + * <code>request</code> argument. The headers that should be sent + * in the reply must be specified in the <code>reply</code> argument. + * + * @param request contains the headers sent by the client; + * <code>request</code> will never be <code>null</code> + * + * @param reply the headers that should be sent in the reply; + * <code>reply</code> will never be <code>null</code> + * + * @param backup <code>true</code> if the client requests that the server + * back up one directory before changing to the path described by + * <code>name</code>; <code>false</code> to apply the request to the present + * path + * + * @param create <code>true</code> if the path should be created if it does + * not already exist; <code>false</code> if the path should not be created + * if it does not exist and an error code should be returned + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onSetPath(HeaderSet request, HeaderSet reply, boolean backup, boolean create) { + + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when a DELETE request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onDelete()</code> will always return an + * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. + * <P> + * The headers received in the request can be retrieved from the + * <code>request</code> argument. The headers that should be sent + * in the reply must be specified in the <code>reply</code> argument. + * + * @param request contains the headers sent by the client; + * <code>request</code> will never be <code>null</code> + * + * @param reply the headers that should be sent in the reply; + * <code>reply</code> will never be <code>null</code> + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onDelete(HeaderSet request, HeaderSet reply) { + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when a PUT request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onPut()</code> will always return an + * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. + * <P> + * If an ABORT request is received during the processing of a PUT request, + * <code>op</code> will be closed by the implementation. + * + * @param op contains the headers sent by the client and allows new + * headers to be sent in the reply; <code>op</code> will never be + * <code>null</code> + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onPut(Operation op) { + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when a GET request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onGet()</code> will always return an + * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. + * <P> + * If an ABORT request is received during the processing of a GET request, + * <code>op</code> will be closed by the implementation. + * + * @param op contains the headers sent by the client and allows new + * headers to be sent in the reply; <code>op</code> will never be + * <code>null</code> + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onGet(Operation op) { + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when this object attempts to authenticate a client and the + * authentication request fails because the response digest in the + * authentication response header was wrong. + * <P> + * If this method is not implemented by the class that extends this class, + * this method will do nothing. + * + * @param userName the user name returned in the authentication response; + * <code>null</code> if no user name was provided in the response + */ + public void onAuthenticationFailure(byte[] userName) { + } + + /**Called by ServerSession to update the status of current transaction */ + public void updateStatus(String message) { + } + + public void onClose() { + } +} diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java new file mode 100644 index 0000000..9daa6c0 --- /dev/null +++ b/obex/javax/obex/ServerSession.java @@ -0,0 +1,922 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * This class in an implementation of the ServerSession interface. + * + * @hide + */ +public class ServerSession implements Runnable, ObexSession { + + private ObexTransport client; + + private InputStream input; + + private OutputStream output; + + private ServerRequestHandler listener; + + private Thread processThread; + + private int maxPacketLength; + + private Authenticator authenticator; + + byte[] challengeDigest; + + private boolean isClosed; + + /** + * Creates new ServerSession. + * + * @param conn + * the connection to the client + * + * @param handler + * the event listener that will process requests + * + * @param auth + * the authenticator to use with this connection + * + * @throws IOException + * if an error occurred while opening the input and output + * streams + */ + public ServerSession(ObexTransport conn, ServerRequestHandler handler, Authenticator auth) + throws IOException { + authenticator = auth; + client = conn; + input = client.openInputStream(); + output = client.openOutputStream(); + listener = handler; + maxPacketLength = 256; + + isClosed = false; + processThread = new Thread(this); + processThread.start(); + } + + /* removed as they're provided to the API user. Not used internally. */ + /* + public boolean isCreatedServer() { + if (client instanceof BTConnection) + return ((BTConnection)client).isServerCreated(); + else + return false; + } + + public boolean isClosed() { + if (client instanceof BTConnection) + return ((BTConnection)client).isClosed(); + else + return false; + } + + public int getConnectionHandle() { + if (client instanceof BTConnection) + return ((BTConnection)client).getConnectionHandle(); + else + return -1; + } + + public RemoteDevice getRemoteDevice() { + if (client instanceof BTConnection) + return ((BTConnection)client).getRemoteDevice(); + else + return null; + }*/ + + /** + * Processes requests made to the server and forwards them to the + * appropriate event listener. + */ + public void run() { + try { + + boolean done = false; + while (!done && !isClosed) { + int requestType = input.read(); + switch (requestType) { + case 0x80: + handleConnectRequest(); + break; + + case 0x81: + handleDisconnectRequest(); + done = true; + break; + + case 0x03: + case 0x83: + handleGetRequest(requestType); + break; + + case 0x02: + case 0x82: + handlePutRequest(requestType); + break; + + case 0x85: + handleSetPathRequest(); + break; + + case -1: + done = true; + break; + + default: + + /* + * Received a request type that is not recognized so I am + * just going to read the packet and send a not implemented + * to the client + */ + int length = input.read(); + length = (length << 8) + input.read(); + for (int i = 3; i < length; i++) { + input.read(); + } + sendResponse(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED, null); + + // done = true; + } + } + + } catch (NullPointerException e) { + } catch (Exception e) { + } + close(); + } + + /** + * Handles a PUT request from a client. This method will provide a + * <code>ServerOperation</code> object to the request handler. The + * <code>ServerOperation</code> object will handle the rest of the request. + * It will also send replies and receive requests until the final reply + * should be sent. When the final reply should be sent, this method will get + * the response code to use and send the reply. The + * <code>ServerOperation</code> object will always reply with a + * OBEX_HTTP_CONTINUE reply. It will only reply if further information is + * needed. + * + * @param type + * the type of request received; either 0x02 or 0x82 + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handlePutRequest(int type) throws IOException { + ServerOperation client = new ServerOperation(this, input, type, maxPacketLength, listener); + try { + int response = -1; + + if ((client.finalBitSet) && !client.isValidBody()) { + response = validateResponseCode(listener.onDelete(client.requestHeaders, + client.replyHeaders)); + } else { + response = validateResponseCode(listener.onPut(client)); + } + if (response != ResponseCodes.OBEX_HTTP_OK) { + client.sendReply(response); + } else if (!client.isAborted) { + // wait for the final bit + while (!client.finalBitSet) { + client.sendReply(ObexHelper.OBEX_HTTP_CONTINUE); + } + client.sendReply(response); + } + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + } + } + + /** + * Handles a GET request from a client. This method will provide a + * <code>ServerOperation</code> object to the request handler. The + * <code>ServerOperation</code> object will handle the rest of the request. + * It will also send replies and receive requests until the final reply + * should be sent. When the final reply should be sent, this method will get + * the response code to use and send the reply. The + * <code>ServerOperation</code> object will always reply with a + * OBEX_HTTP_CONTINUE reply. It will only reply if further information is + * needed. + * + * @param type + * the type of request received; either 0x03 or 0x83 + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handleGetRequest(int type) throws IOException { + ServerOperation client = new ServerOperation(this, input, type, maxPacketLength, listener); + try { + int response = validateResponseCode(listener.onGet(client)); + + if (!client.isAborted) { + client.sendReply(response); + } + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + } + } + + /** + * Send standard response. + * + * @param code + * the response code to send + * + * @param header + * the headers to include in the response + * + * @throws IOException + * if an IO error occurs + */ + protected void sendResponse(int code, byte[] header) throws IOException { + int totalLength = 3; + byte[] data = null; + + if (header != null) { + totalLength += header.length; + data = new byte[totalLength]; + data[0] = (byte)code; + data[1] = (byte)(totalLength >> 8); + data[2] = (byte)totalLength; + System.arraycopy(header, 0, data, 3, header.length); + } else { + data = new byte[totalLength]; + data[0] = (byte)code; + data[1] = (byte)0x00; + data[2] = (byte)totalLength; + } + output.write(data); + output.flush(); + } + + /** + * Handles a SETPATH request from a client. This method will read the rest + * of the request from the client. Assuming the request is valid, it will + * create a <code>HeaderSet</code> object to pass to the + * <code>ServerRequestHandler</code> object. After the handler processes the + * request, this method will create a reply message to send to the server + * with the response code provided. + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handleSetPathRequest() throws IOException { + int length; + int flags; + int constants; + int totalLength = 3; + byte[] head = null; + int code = -1; + int bytesReceived; + HeaderSet request = new HeaderSet(); + HeaderSet reply = new HeaderSet(); + + length = input.read(); + length = (length << 8) + input.read(); + flags = input.read(); + constants = input.read(); + + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 3; + } else { + if (length > 5) { + byte[] headers = new byte[length - 5]; + bytesReceived = input.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += input.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + ObexHelper.updateHeaderSet(request, headers); + + if (request.connectionID != null) { + listener.setConnectionID(ObexHelper.convertToLong(request.connectionID)); + } else { + listener.setConnectionID(-1); + } + // the Auth chan is initiated by the server. + // client sent back the authResp . + if (request.authResp != null) { + if (!handleAuthResp(request.authResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + listener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, + request.authResp)); + } + request.authResp = null; + } + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + // the Auth chan is initiated by the client + // the server will send back the authResp to the client + if (request.authChall != null) { + handleAuthChall(request); + reply.authResp = new byte[request.authResp.length]; + System.arraycopy(request.authResp, 0, reply.authResp, 0, reply.authResp.length); + request.authChall = null; + request.authResp = null; + } + boolean backup = false; + boolean create = true; + if (!((flags & 1) == 0)) { + backup = true; + } + if ((flags & 2) == 0) { + create = false; + } + + try { + code = listener.onSetPath(request, reply, backup, create); + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + return; + } + + code = validateResponseCode(code); + + if (reply.nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16); + } else { + challengeDigest = null; + } + + long id = listener.getConnectionID(); + if (id == -1) { + reply.connectionID = null; + } else { + reply.connectionID = ObexHelper.convertToByteArray(id); + } + + head = ObexHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > maxPacketLength) { + totalLength = 3; + head = null; + code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + } + } + + // Compute Length of OBEX SETPATH packet + byte[] replyData = new byte[totalLength]; + replyData[0] = (byte)code; + replyData[1] = (byte)(totalLength >> 8); + replyData[2] = (byte)totalLength; + if (head != null) { + System.arraycopy(head, 0, replyData, 3, head.length); + } + /* + * Write the OBEX SETPATH packet to the server. Byte 0: response code + * Byte 1&2: Connect Packet Length Byte 3 to n: headers + */ + output.write(replyData); + output.flush(); + } + + /** + * Handles a disconnect request from a client. This method will read the + * rest of the request from the client. Assuming the request is valid, it + * will create a <code>HeaderSet</code> object to pass to the + * <code>ServerRequestHandler</code> object. After the handler processes the + * request, this method will create a reply message to send to the server. + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handleDisconnectRequest() throws IOException { + int length; + int code = ResponseCodes.OBEX_HTTP_OK; + int totalLength = 3; + byte[] head = null; + int bytesReceived; + HeaderSet request = new HeaderSet(); + HeaderSet reply = new HeaderSet(); + + length = input.read(); + length = (length << 8) + input.read(); + + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 3; + } else { + if (length > 3) { + byte[] headers = new byte[length - 3]; + bytesReceived = input.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += input.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + ObexHelper.updateHeaderSet(request, headers); + } + + if (request.connectionID != null) { + listener.setConnectionID(ObexHelper.convertToLong(request.connectionID)); + } else { + listener.setConnectionID(1); + } + + if (request.authResp != null) { + if (!handleAuthResp(request.authResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + listener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, + request.authResp)); + } + request.authResp = null; + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + + if (request.authChall != null) { + handleAuthChall(request); + request.authChall = null; + } + + try { + listener.onDisconnect(request, reply); + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + return; + } + + /* + * Since a client will never response to an authentication + * challenge on a DISCONNECT, there is no reason to keep track + * of the challenge. + * + * if (reply.nonce != null) { challengeDigest = new byte[16]; + * System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16); } + * else { challengeDigest = null; } + */ + + long id = listener.getConnectionID(); + if (id == -1) { + reply.connectionID = null; + } else { + reply.connectionID = ObexHelper.convertToByteArray(id); + } + + head = ObexHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > maxPacketLength) { + totalLength = 3; + head = null; + code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + } + } + + // Compute Length of OBEX CONNECT packet + byte[] replyData; + if (head != null) { + replyData = new byte[3 + head.length]; + } else { + replyData = new byte[3]; + } + replyData[0] = (byte)code; + replyData[1] = (byte)(totalLength >> 8); + replyData[2] = (byte)totalLength; + if (head != null) { + System.arraycopy(head, 0, replyData, 3, head.length); + } + /* + * Write the OBEX DISCONNECT packet to the server. Byte 0: response code + * Byte 1&2: Connect Packet Length Byte 3 to n: headers + */ + output.write(replyData); + output.flush(); + } + + /** + * Handles a connect request from a client. This method will read the rest + * of the request from the client. Assuming the request is valid, it will + * create a <code>HeaderSet</code> object to pass to the + * <code>ServerRequestHandler</code> object. After the handler processes the + * request, this method will create a reply message to send to the server + * with the response code provided. + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handleConnectRequest() throws IOException { + int packetLength; + int version; + int flags; + int totalLength = 7; + byte[] head = null; + int code = -1; + HeaderSet request = new HeaderSet(); + HeaderSet reply = new HeaderSet(); + int bytesReceived; + + /* + * Read in the length of the OBEX packet, OBEX version, flags, and max + * packet length + */ + packetLength = input.read(); + packetLength = (packetLength << 8) + input.read(); + version = input.read(); + flags = input.read(); + maxPacketLength = input.read(); + maxPacketLength = (maxPacketLength << 8) + input.read(); + + // should we check it? + if (maxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) { + maxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT; + } + + if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 7; + } else { + if (packetLength > 7) { + byte[] headers = new byte[packetLength - 7]; + bytesReceived = input.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += input.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + ObexHelper.updateHeaderSet(request, headers); + } + + if (request.connectionID != null) { + listener.setConnectionID(ObexHelper.convertToLong(request.connectionID)); + } else { + listener.setConnectionID(1); + } + + if (request.authResp != null) { + if (!handleAuthResp(request.authResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + listener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, + request.authResp)); + } + request.authResp = null; + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + if (request.authChall != null) { + handleAuthChall(request); + reply.authResp = new byte[request.authResp.length]; + System.arraycopy(request.authResp, 0, reply.authResp, 0, reply.authResp.length); + request.authChall = null; + request.authResp = null; + } + + try { + code = listener.onConnect(request, reply); + code = validateResponseCode(code); + + if (reply.nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16); + } else { + challengeDigest = null; + } + long id = listener.getConnectionID(); + if (id == -1) { + reply.connectionID = null; + } else { + reply.connectionID = ObexHelper.convertToByteArray(id); + } + + head = ObexHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > maxPacketLength) { + totalLength = 7; + head = null; + code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + } catch (Exception e) { + e.printStackTrace(); + totalLength = 7; + head = null; + code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + } + } + + // Compute Length of OBEX CONNECT packet + byte[] length = ObexHelper.convertToByteArray(totalLength); + + /* + * Write the OBEX CONNECT packet to the server. Byte 0: response code + * Byte 1&2: Connect Packet Length Byte 3: OBEX Version Number + * (Presently, 0x10) Byte 4: Flags (For TCP 0x00) Byte 5&6: Max OBEX + * Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers + */ + byte[] sendData = new byte[totalLength]; + sendData[0] = (byte)code; + sendData[1] = length[2]; + sendData[2] = length[3]; + sendData[3] = (byte)0x10; + sendData[4] = (byte)0x00; + sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); + sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); + + if (head != null) { + System.arraycopy(head, 0, sendData, 7, head.length); + } + + output.write(sendData); + output.flush(); + } + + /** + * Closes the server session - in detail close I/O streams and the + * underlying transport layer. Internal flag is also set so that later + * attempt to read/write will throw an exception. + */ + public synchronized void close() { + if (listener != null) { + listener.onClose(); + } + try { + input.close(); + output.close(); + client.close(); + isClosed = true; + } catch (Exception e) { + } + client = null; + input = null; + output = null; + listener = null; + } + + /** + * Verifies that the response code is valid. If it is not valid, it will + * return the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code. + * + * @param code + * the response code to check + * + * @return the valid response code or <code>OBEX_HTTP_INTERNAL_ERROR</code> + * if <code>code</code> is not valid + */ + private int validateResponseCode(int code) { + + if ((code >= ResponseCodes.OBEX_HTTP_OK) && (code <= ResponseCodes.OBEX_HTTP_PARTIAL)) { + return code; + } + if ((code >= ResponseCodes.OBEX_HTTP_MULT_CHOICE) + && (code <= ResponseCodes.OBEX_HTTP_USE_PROXY)) { + return code; + } + if ((code >= ResponseCodes.OBEX_HTTP_BAD_REQUEST) + && (code <= ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE)) { + return code; + } + if ((code >= ResponseCodes.OBEX_HTTP_INTERNAL_ERROR) + && (code <= ResponseCodes.OBEX_HTTP_VERSION)) { + return code; + } + if ((code >= ResponseCodes.OBEX_DATABASE_FULL) + && (code <= ResponseCodes.OBEX_DATABASE_LOCKED)) { + return code; + } + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + /** + * Called when the server received an authentication challenge header. This + * will cause the authenticator to handle the authentication challenge. + * + * @param header + * the header with the authentication challenge + * + * @return <code>true</code> if the last request should be resent; + * <code>false</code> if the last request should not be resent + */ + protected boolean handleAuthChall(HeaderSet header) { + if (authenticator == null) { + return false; + } + + /* + * An authentication challenge is made up of one required and two + * optional tag length value triplets. The tag 0x00 is required to be in + * the authentication challenge and it represents the challenge digest + * that was received. The tag 0x01 is the options tag. This tag tracks + * if user ID is required and if full access will be granted. The tag + * 0x02 is the realm, which provides a description of which user name + * and password to use. + */ + byte[] challenge = ObexHelper.getTagValue((byte)0x00, header.authChall); + byte[] option = ObexHelper.getTagValue((byte)0x01, header.authChall); + byte[] description = ObexHelper.getTagValue((byte)0x02, header.authChall); + + String realm = ""; + if (description != null) { + byte[] realmString = new byte[description.length - 1]; + System.arraycopy(description, 1, realmString, 0, realmString.length); + + switch (description[0] & 0xFF) { + + case 0x00: + // ASCII encoding + // Fall through + case 0x01: + // ISO-8859-1 encoding + try { + realm = new String(realmString, "ISO8859_1"); + } catch (Exception e) { + throw new RuntimeException("Unsupported Encoding Scheme"); + } + break; + + case 0xFF: + // UNICODE Encoding + realm = ObexHelper.convertToUnicode(realmString, false); + break; + + case 0x02: + // ISO-8859-2 encoding + // Fall through + case 0x03: + // ISO-8859-3 encoding + // Fall through + case 0x04: + // ISO-8859-4 encoding + // Fall through + case 0x05: + // ISO-8859-5 encoding + // Fall through + case 0x06: + // ISO-8859-6 encoding + // Fall through + case 0x07: + // ISO-8859-7 encoding + // Fall through + case 0x08: + // ISO-8859-8 encoding + // Fall through + case 0x09: + // ISO-8859-9 encoding + // Fall through + default: + throw new RuntimeException("Unsupported Encoding Scheme"); + } + } + + boolean isUserIDRequired = false; + boolean isFullAccess = true; + if (option != null) { + if ((option[0] & 0x01) != 0) { + isUserIDRequired = true; + } + + if ((option[0] & 0x02) != 0) { + isFullAccess = false; + } + } + + PasswordAuthentication result = null; + header.authChall = null; + + try { + result = authenticator.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); + } catch (Exception e) { + return false; + } + + /* + * If no password is provided then we not resent the request + */ + if (result == null) { + return false; + } + + byte[] password = result.getPassword(); + if (password == null) { + return false; + } + + byte[] userName = result.getUserName(); + + /* + * Create the authentication response header. It includes 1 required and + * 2 option tag length value triples. The required triple has a tag of + * 0x00 and is the response digest. The first optional tag is 0x01 and + * represents the user ID. If no user ID is provided, then no user ID + * will be sent. The second optional tag is 0x02 and is the challenge + * that was received. This will always be sent + */ + if (userName != null) { + header.authResp = new byte[38 + userName.length]; + header.authResp[36] = (byte)0x01; + header.authResp[37] = (byte)userName.length; + System.arraycopy(userName, 0, header.authResp, 38, userName.length); + } else { + header.authResp = new byte[36]; + } + + // Create the secret String + byte[] digest = new byte[challenge.length + password.length + 1]; + System.arraycopy(challenge, 0, digest, 0, challenge.length); + // Insert colon between challenge and password + digest[challenge.length] = (byte)0x3A; + System.arraycopy(password, 0, digest, challenge.length + 1, password.length); + + // Add the Response Digest + header.authResp[0] = (byte)0x00; + header.authResp[1] = (byte)0x10; + + System.arraycopy(ObexHelper.computeMd5Hash(digest), 0, header.authResp, 2, 16); + + // Add the challenge + header.authResp[18] = (byte)0x02; + header.authResp[19] = (byte)0x10; + System.arraycopy(challenge, 0, header.authResp, 20, 16); + + return true; + } + + /** + * Called when the server received an authentication response header. This + * will cause the authenticator to handle the authentication response. + * + * @param authResp + * the authentication response + * + * @return <code>true</code> if the response passed; <code>false</code> if + * the response failed + */ + protected boolean handleAuthResp(byte[] authResp) { + if (authenticator == null) { + return false; + } + // get the correct password from the application + byte[] correctPassword = authenticator.onAuthenticationResponse(ObexHelper.getTagValue( + (byte)0x01, authResp)); + if (correctPassword == null) { + return false; + } + + byte[] temp = new byte[correctPassword.length + 16]; + + System.arraycopy(challengeDigest, 0, temp, 0, 16); + System.arraycopy(correctPassword, 0, temp, 16, correctPassword.length); + + byte[] correctResponse = ObexHelper.computeMd5Hash(temp); + byte[] actualResponse = ObexHelper.getTagValue((byte)0x00, authResp); + + // compare the MD5 hash array . + for (int i = 0; i < 16; i++) { + if (correctResponse[i] != actualResponse[i]) { + return false; + } + } + + return true; + } +} diff --git a/obex/javax/obex/SessionNotifier.java b/obex/javax/obex/SessionNotifier.java new file mode 100644 index 0000000..fd574c0 --- /dev/null +++ b/obex/javax/obex/SessionNotifier.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * 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. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 HOLDER 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 javax.obex; + +import java.io.IOException; + +/** + * The <code>SessionNotifier</code> interface defines a connection notifier for + * server-side OBEX connections. When a <code>SessionNotifier</code> is + * created and calls <code>acceptAndOpen()</code>, it will begin listening for + * clients to create a connection at the transport layer. When the transport + * layer connection is received, the <code>acceptAndOpen()</code> method will + * return a <code>javax.microedition.io.Connection</code> that is the + * connection to the client. The <code>acceptAndOpen()</code> method also takes a + * <code>ServerRequestHandler</code> argument that will process the requests + * from the client that connects to the server. + * + * @hide + */ +public interface SessionNotifier { + + /** + * Waits for a transport layer connection to be established and specifies + * the handler to handle the requests from the client. No authenticator + * is associated with this connection, therefore, it is implementation + * dependent as to how an authentication challenge and authentication + * response header will be received and processed. + * <P> + * <H4>Additional Note for OBEX over Bluetooth</H4> + * If this method is called on a <code>SessionNotifier</code> object that + * does not have a <code>ServiceRecord</code> in the SDDB, the + * <code>ServiceRecord</code> for this object will be added to the SDDB. + * This method requests the BCC to put the + * local device in connectable mode so that it will respond to + * connection attempts by clients. + * <P> + * The following checks are done to verify that the service record + * provided is valid. If any of these checks fail, then a + * <code>ServiceRegistrationException</code> is thrown. + * <UL> + * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory + * service attributes for a <code>btgoep</code> service record, must be + * present in the <code>ServiceRecord</code> associated with this notifier. + * <LI>L2CAP, RFCOMM and OBEX must all be in the ProtocolDescriptorList + * <LI>The <code>ServiceRecord</code> associated with this notifier must + * not have changed the RFCOMM server channel number + * </UL> + * <P> + * This method will not ensure that <code>ServiceRecord</code> associated + * with this notifier is a completely + * valid service record. It is the responsibility of the application to + * ensure that the service record follows all of the applicable + * syntactic and semantic rules for service record correctness. + * + * @param handler the request handler that will respond to OBEX requests + * + * @return the connection to the client + * + * @throws IOException if an error occurs in the transport layer + * + * @throws NullPointerException if <code>handler</code> is + * <code>null</code> + * + * @throws ServiceRegistrationException if the structure of the + * associated service record is invalid or if the service record + * could not be added successfully to the local SDDB. The + * structure of service record is invalid if the service + * record is missing any mandatory service attributes, or has + * changed any of the values described above which are fixed and + * cannot be changed. Failures to add the record to the SDDB could + * be due to insufficient disk space, database locks, etc. + * + * @throws BluetoothStateException if the server device could + * not be placed in connectable mode because the device user has + * configured the device to be non-connectable + */ + public ObexSession acceptAndOpen(ServerRequestHandler handler) throws IOException; + + /** + * Waits for a transport layer connection to be established and specifies + * the handler to handle the requests from the client and the + * <code>Authenticator</code> to use to respond to authentication challenge + * and authentication response headers. + * <P> + * <H4>Additional Note for OBEX over Bluetooth</H4> + * If this method is called on a <code>SessionNotifier</code> object that + * does not have a <code>ServiceRecord</code> in the SDDB, the + * <code>ServiceRecord</code> for this object will be added to the SDDB. + * This method requests the BCC to put the + * local device in connectable mode so that it will respond to + * connection attempts by clients. + * <P> + * The following checks are done to verify that the service record + * provided is valid. If any of these checks fail, then a + * <code>ServiceRegistrationException</code> is thrown. + * <UL> + * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory + * service attributes for a <code>btgoep</code> service record, must be + * present in the <code>ServiceRecord</code> associated with this notifier. + * <LI>L2CAP, RFCOMM and OBEX must all be in the ProtocolDescriptorList + * <LI>The <code>ServiceRecord</code> associated with this notifier must + * not have changed the RFCOMM server channel number + * </UL> + * <P> + * This method will not ensure that <code>ServiceRecord</code> associated + * with this notifier is a completely + * valid service record. It is the responsibility of the application to + * ensure that the service record follows all of the applicable + * syntactic and semantic rules for service record correctness. + * + * @param handler the request handler that will respond to OBEX requests + * + * @param auth the <code>Authenticator</code> to use with this connection; + * if <code>null</code> then no <code>Authenticator</code> will be used + * + * @return the connection to the client + * + * @throws IOException if an error occurs in the transport layer + * + * @throws NullPointerException if <code>handler</code> is + * <code>null</code> + * + * @throws ServiceRegistrationException if the structure of the + * associated service record is invalid or if the service record + * could not be added successfully to the local SDDB. The + * structure of service record is invalid if the service + * record is missing any mandatory service attributes, or has + * changed any of the values described above which are fixed and + * cannot be changed. Failures to add the record to the SDDB could + * be due to insufficient disk space, database locks, etc. + * + * @throws BluetoothStateException if the server device could + * not be placed in connectable mode because the device user has + * configured the device to be non-connectable + */ + public ObexSession acceptAndOpen(ServerRequestHandler handler, Authenticator auth) + throws IOException; +} diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index c283418..87f4c40 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -36,6 +36,7 @@ user opt-in via Setup Wizard or Settings. --> <string name="def_location_providers_allowed">gps</string> + <bool name="assisted_gps_enabled">true</bool> <!-- 0 == mobile, 1 == wifi. --> <integer name="def_network_preference">1</integer> <bool name="def_usb_mass_storage_enabled">true</bool> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index f861400..602f3e1 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -64,7 +64,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "SettingsProvider"; private static final String DATABASE_NAME = "settings.db"; - private static final int DATABASE_VERSION = 34; + private static final int DATABASE_VERSION = 35; private Context mContext; @@ -386,6 +386,21 @@ public class DatabaseHelper extends SQLiteOpenHelper { upgradeVersion = 34; } + if (upgradeVersion == 34) { + db.beginTransaction(); + try { + String value = + mContext.getResources().getBoolean(R.bool.assisted_gps_enabled) ? "1" : "0"; + db.execSQL("INSERT OR IGNORE INTO secure(name,value) values('" + + Settings.Secure.ASSISTED_GPS_ENABLED + "','" + value + "');"); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + upgradeVersion = 35; + } + if (upgradeVersion != currentVersion) { Log.w(TAG, "Got stuck trying to upgrade from version " + upgradeVersion + ", must wipe the settings provider"); @@ -592,6 +607,21 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT, R.integer.def_screen_off_timeout); + // Set default cdma emergency tone + loadSetting(stmt, Settings.System.EMERGENCY_TONE, 0); + + // Set default cdma call auto retry + loadSetting(stmt, Settings.System.CALL_AUTO_RETRY, 0); + + // Set default cdma DTMF type + loadSetting(stmt, Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, 0); + + // Set default hearing aid + loadSetting(stmt, Settings.System.HEARING_AID, 0); + + // Set default tty mode + loadSetting(stmt, Settings.System.TTY_MODE, 0); + loadBooleanSetting(stmt, Settings.System.AIRPLANE_MODE_ON, R.bool.def_airplane_mode_on); @@ -638,6 +668,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { loadStringSetting(stmt, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, R.string.def_location_providers_allowed); + loadBooleanSetting(stmt, Settings.Secure.ASSISTED_GPS_ENABLED, + R.bool.assisted_gps_enabled); + loadIntegerSetting(stmt, Settings.Secure.NETWORK_PREFERENCE, R.integer.def_network_preference); diff --git a/packages/TtsService/AndroidManifest.xml b/packages/TtsService/AndroidManifest.xml index 1dc25c6..fab2534 100755 --- a/packages/TtsService/AndroidManifest.xml +++ b/packages/TtsService/AndroidManifest.xml @@ -12,4 +12,5 @@ </service> </application> <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> </manifest> diff --git a/packages/TtsService/jni/android_tts_SynthProxy.cpp b/packages/TtsService/jni/android_tts_SynthProxy.cpp index 0dafcc1..a55b704 100644 --- a/packages/TtsService/jni/android_tts_SynthProxy.cpp +++ b/packages/TtsService/jni/android_tts_SynthProxy.cpp @@ -199,16 +199,29 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate, if (wav == NULL) { delete pForAfter; LOGV("Null: speech has completed"); + return TTS_CALLBACK_HALT; } if (bufferSize > 0){ fwrite(wav, 1, bufferSize, pForAfter->outputFile); } } - // TODO update to call back into the SynthProxy class through the + // Future update: + // For sync points in the speech, call back into the SynthProxy class through the // javaTTSFields.synthProxyMethodPost methode to notify - // playback has completed if the synthesis is done, i.e. - // if status == TTS_SYNTH_DONE - //delete pForAfter; + // playback has completed if the synthesis is done or if a marker has been reached. + + if (status == TTS_SYNTH_DONE) { + // this struct was allocated in the original android_tts_SynthProxy_speak call, + // all processing matching this call is now done. + LOGV("Speech synthesis done."); + if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY) { + // only delete for direct playback. When writing to a file, we still have work to do + // in android_tts_SynthProxy_synthesizeToFile. The struct will be deleted there. + delete pForAfter; + pForAfter = NULL; + } + return TTS_CALLBACK_HALT; + } // we don't update the wav (output) parameter as we'll let the next callback // write at the same location, we've consumed the data already, but we need @@ -270,6 +283,33 @@ android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData) } +static int +android_tts_SynthProxy_isLanguageAvailable(JNIEnv *env, jobject thiz, jint jniData, + jstring language, jstring country, jstring variant) +{ + int result = TTS_LANG_NOT_SUPPORTED; + + if (jniData == 0) { + LOGE("android_tts_SynthProxy_isLanguageAvailable(): invalid JNI data"); + return result; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + const char *langNativeString = env->GetStringUTFChars(language, 0); + const char *countryNativeString = env->GetStringUTFChars(country, 0); + const char *variantNativeString = env->GetStringUTFChars(variant, 0); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + result = pSynthData->mNativeSynthInterface->isLanguageAvailable(langNativeString, + countryNativeString, variantNativeString); + } + env->ReleaseStringUTFChars(language, langNativeString); + env->ReleaseStringUTFChars(country, countryNativeString); + env->ReleaseStringUTFChars(variant, variantNativeString); + return result; +} + + static void android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData, jstring language, jstring country, jstring variant) @@ -289,8 +329,32 @@ android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData, variantNativeString); } env->ReleaseStringUTFChars(language, langNativeString); - env->ReleaseStringUTFChars(language, countryNativeString); - env->ReleaseStringUTFChars(language, variantNativeString); + env->ReleaseStringUTFChars(country, countryNativeString); + env->ReleaseStringUTFChars(variant, variantNativeString); +} + + +static void +android_tts_SynthProxy_loadLanguage(JNIEnv *env, jobject thiz, jint jniData, + jstring language, jstring country, jstring variant) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_loadLanguage(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + const char *langNativeString = env->GetStringUTFChars(language, 0); + const char *countryNativeString = env->GetStringUTFChars(country, 0); + const char *variantNativeString = env->GetStringUTFChars(variant, 0); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->loadLanguage(langNativeString, countryNativeString, + variantNativeString); + } + env->ReleaseStringUTFChars(language, langNativeString); + env->ReleaseStringUTFChars(country, countryNativeString); + env->ReleaseStringUTFChars(variant, variantNativeString); } @@ -338,7 +402,6 @@ android_tts_SynthProxy_setPitch(JNIEnv *env, jobject thiz, jint jniData, } -// TODO: Refactor this to get rid of any assumptions about sample rate, etc. static void android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, jstring textJavaString, jstring filenameJavaString) @@ -349,6 +412,21 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, } SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + if (!pSynthData->mNativeSynthInterface) { + LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid engine handle"); + return; + } + + // Retrieve audio parameters before writing the file header + AudioSystem::audio_format encoding = DEFAULT_TTS_FORMAT; + uint32_t rate = DEFAULT_TTS_RATE; + int channels = DEFAULT_TTS_NB_CHANNELS; + pSynthData->mNativeSynthInterface->setAudioFormat(encoding, rate, channels); + + if ((encoding != AudioSystem::PCM_16_BIT) && (encoding != AudioSystem::PCM_8_BIT)) { + LOGE("android_tts_SynthProxy_synthesizeToFile(): engine uses invalid format"); + return; + } const char *filenameNativeString = env->GetStringUTFChars(filenameJavaString, 0); @@ -360,6 +438,12 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, pForAfter->outputFile = fopen(filenameNativeString, "wb"); + if (pForAfter->outputFile == NULL) { + LOGE("android_tts_SynthProxy_synthesizeToFile(): error creating output file"); + delete pForAfter; + return; + } + // Write 44 blank bytes for WAV header, then come back and fill them in // after we've written the audio data char header[44]; @@ -368,10 +452,8 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, unsigned int unique_identifier; // TODO check return codes - if (pSynthData->mNativeSynthInterface) { - pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, pSynthData->mBufferSize, - (void *)pForAfter); - } + pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, + pSynthData->mBufferSize, (void *)pForAfter); long filelen = ftell(pForAfter->outputFile); @@ -393,12 +475,14 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, ((uint32_t *)(&header[16]))[0] = 16; // size of fmt + int sampleSizeInByte = (encoding == AudioSystem::PCM_16_BIT ? 2 : 1); + ((unsigned short *)(&header[20]))[0] = 1; // format - ((unsigned short *)(&header[22]))[0] = 1; // channels - ((uint32_t *)(&header[24]))[0] = 22050; // samplerate - ((uint32_t *)(&header[28]))[0] = 44100; // byterate - ((unsigned short *)(&header[32]))[0] = 2; // block align - ((unsigned short *)(&header[34]))[0] = 16; // bits per sample + ((unsigned short *)(&header[22]))[0] = channels; // channels + ((uint32_t *)(&header[24]))[0] = rate; // samplerate + ((uint32_t *)(&header[28]))[0] = rate * sampleSizeInByte * channels;// byterate + ((unsigned short *)(&header[32]))[0] = sampleSizeInByte * channels; // block align + ((unsigned short *)(&header[34]))[0] = sampleSizeInByte * 8; // bits per sample header[36] = 'd'; header[37] = 'a'; @@ -414,6 +498,9 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, fflush(pForAfter->outputFile); fclose(pForAfter->outputFile); + delete pForAfter; + pForAfter = NULL; + env->ReleaseStringUTFChars(textJavaString, textNativeString); env->ReleaseStringUTFChars(filenameJavaString, filenameNativeString); } @@ -441,8 +528,8 @@ android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, if (pSynthData->mNativeSynthInterface) { const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); - pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, pSynthData->mBufferSize, - (void *)pForAfter); + pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, + pSynthData->mBufferSize, (void *)pForAfter); env->ReleaseStringUTFChars(textJavaString, textNativeString); } } @@ -501,23 +588,34 @@ LOGI("android_tts_SynthProxy_playAudioBuffer"); } -JNIEXPORT jstring JNICALL +static jobjectArray android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData) { if (jniData == 0) { LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data"); - return env->NewStringUTF(""); + return NULL; } SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - size_t bufSize = 100; - char buf[bufSize]; - memset(buf, 0, bufSize); - // TODO check return codes + if (pSynthData->mNativeSynthInterface) { - pSynthData->mNativeSynthInterface->getLanguage(buf, &bufSize); + size_t bufSize = 100; + char lang[bufSize]; + char country[bufSize]; + char variant[bufSize]; + memset(lang, 0, bufSize); + memset(country, 0, bufSize); + memset(variant, 0, bufSize); + jobjectArray retLocale = (jobjectArray)env->NewObjectArray(3, + env->FindClass("java/lang/String"), env->NewStringUTF("")); + pSynthData->mNativeSynthInterface->getLanguage(lang, country, variant); + env->SetObjectArrayElement(retLocale, 0, env->NewStringUTF(lang)); + env->SetObjectArrayElement(retLocale, 1, env->NewStringUTF(country)); + env->SetObjectArrayElement(retLocale, 2, env->NewStringUTF(variant)); + return retLocale; + } else { + return NULL; } - return env->NewStringUTF(buf); } @@ -555,10 +653,18 @@ static JNINativeMethod gMethods[] = { "(ILjava/lang/String;Ljava/lang/String;)V", (void*)android_tts_SynthProxy_synthesizeToFile }, + { "native_isLanguageAvailable", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + (void*)android_tts_SynthProxy_isLanguageAvailable + }, { "native_setLanguage", "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", (void*)android_tts_SynthProxy_setLanguage }, + { "native_loadLanguage", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + (void*)android_tts_SynthProxy_loadLanguage + }, { "native_setSpeechRate", "(II)V", (void*)android_tts_SynthProxy_setSpeechRate @@ -572,7 +678,7 @@ static JNINativeMethod gMethods[] = { (void*)android_tts_SynthProxy_playAudioBuffer }, { "native_getLanguage", - "(I)Ljava/lang/String;", + "(I)[Ljava/lang/String;", (void*)android_tts_SynthProxy_getLanguage }, { "native_getRate", diff --git a/packages/TtsService/src/android/tts/SynthProxy.java b/packages/TtsService/src/android/tts/SynthProxy.java index 3bdff37..91fe3b7 100755 --- a/packages/TtsService/src/android/tts/SynthProxy.java +++ b/packages/TtsService/src/android/tts/SynthProxy.java @@ -68,49 +68,64 @@ public class SynthProxy { // TODO add IPA methods /** - * Sets the language + * Queries for language support. + * Return codes are defined in android.speech.tts.TextToSpeech + */ + public int isLanguageAvailable(String language, String country, String variant) { + return native_isLanguageAvailable(mJniData, language, country, variant); + } + + /** + * Sets the language. */ public void setLanguage(String language, String country, String variant) { native_setLanguage(mJniData, language, country, variant); } /** - * Sets the speech rate + * Loads the language: it's not set, but prepared for use later. + */ + public void loadLanguage(String language, String country, String variant) { + native_loadLanguage(mJniData, language, country, variant); + } + + /** + * Sets the speech rate. */ public final void setSpeechRate(int speechRate) { native_setSpeechRate(mJniData, speechRate); } /** - * Sets the pitch of the synthesized voice + * Sets the pitch of the synthesized voice. */ public final void setPitch(int pitch) { native_setPitch(mJniData, pitch); } /** - * Plays the given audio buffer + * Plays the given audio buffer. */ public void playAudioBuffer(int bufferPointer, int bufferSize) { native_playAudioBuffer(mJniData, bufferPointer, bufferSize); } /** - * Gets the currently set language + * Returns the currently set language, country and variant information. */ - public String getLanguage() { + public String[] getLanguage() { return native_getLanguage(mJniData); } /** - * Gets the currently set rate + * Gets the currently set rate. */ public int getRate() { return native_getRate(mJniData); } /** - * Shuts down the native synthesizer + * Shuts down the native synthesizer. */ public void shutdown() { native_shutdown(mJniData); @@ -147,17 +162,23 @@ public class SynthProxy { private native final void native_synthesizeToFile(int jniData, String text, String filename); + private native final int native_isLanguageAvailable(int jniData, String language, + String country, String variant); + private native final void native_setLanguage(int jniData, String language, String country, String variant); + private native final void native_loadLanguage(int jniData, String language, String country, + String variant); + private native final void native_setSpeechRate(int jniData, int speechRate); - + private native final void native_setPitch(int jniData, int speechRate); // TODO add buffer format private native final void native_playAudioBuffer(int jniData, int bufferPointer, int bufferSize); - private native final String native_getLanguage(int jniData); + private native final String[] native_getLanguage(int jniData); private native final int native_getRate(int jniData); diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java index 6fa3141..b1e6425 100755 --- a/packages/TtsService/src/android/tts/TtsService.java +++ b/packages/TtsService/src/android/tts/TtsService.java @@ -47,12 +47,13 @@ import java.util.concurrent.locks.ReentrantLock; public class TtsService extends Service implements OnCompletionListener { private static class SpeechItem { - public static final int SPEECH = 0; - public static final int EARCON = 1; - public static final int SILENCE = 2; + public static final int TEXT = 0; + public static final int IPA = 1; + public static final int EARCON = 2; + public static final int SILENCE = 3; public String mText = null; public ArrayList<String> mParams = null; - public int mType = SPEECH; + public int mType = TEXT; public long mDuration = 0; public SpeechItem(String text, ArrayList<String> params, int itemType) { @@ -89,6 +90,9 @@ public class TtsService extends Service implements OnCompletionListener { } } + private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000; + private static final int MAX_FILENAME_LENGTH = 250; + private static final String ACTION = "android.intent.action.USE_TTS"; private static final String CATEGORY = "android.intent.category.TTS"; private static final String PKGNAME = "android.tts"; @@ -108,7 +112,6 @@ public class TtsService extends Service implements OnCompletionListener { private final ReentrantLock synthesizerLock = new ReentrantLock(); private SynthProxy nativeSynth; - @Override public void onCreate() { super.onCreate(); @@ -145,13 +148,10 @@ public class TtsService extends Service implements OnCompletionListener { private void setDefaultSettings() { - - // TODO handle default language - setLanguage("eng", "USA", ""); + setLanguage(this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); // speech rate setSpeechRate(getDefaultRate()); - } @@ -217,6 +217,17 @@ public class TtsService extends Service implements OnCompletionListener { } + private int isLanguageAvailable(String lang, String country, String variant) { + Log.v("TTS", "TtsService.isLanguageAvailable(" + lang + ", " + country + ", " +variant+")"); + return nativeSynth.isLanguageAvailable(lang, country, variant); + } + + + private String[] getLanguage() { + return nativeSynth.getLanguage(); + } + + private void setLanguage(String lang, String country, String variant) { Log.v("TTS", "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")"); if (isDefaultEnforced()) { @@ -298,7 +309,29 @@ public class TtsService extends Service implements OnCompletionListener { if (queueMode == 0) { stop(); } - mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH)); + mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.TEXT)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + /** + * Speaks the given IPA text using the specified queueing mode and parameters. + * + * @param ipaText + * The IPA text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. + */ + private void speakIpa(String ipaText, int queueMode, ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(ipaText, params, SpeechItem.IPA)); if (!mIsSpeaking) { processSpeechQueue(); } @@ -392,6 +425,26 @@ public class TtsService extends Service implements OnCompletionListener { synth.start(); return; } + if (params != null){ + String language = ""; + String country = ""; + String variant = ""; + for (int i = 0; i < params.size() - 1; i = i + 2){ + String param = params.get(i); + if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)){ + setSpeechRate(Integer.parseInt(params.get(i+1))); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){ + language = params.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){ + country = params.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){ + variant = params.get(i+1); + } + } + if (language.length() > 0){ + setLanguage(language, country, variant); + } + } nativeSynth.speak(text); } catch (InterruptedException e) { e.printStackTrace(); @@ -424,6 +477,11 @@ public class TtsService extends Service implements OnCompletionListener { return sr; } + private void broadcastTtsQueueProcessingCompleted(){ + Intent i = new Intent(Intent.ACTION_TTS_QUEUE_PROCESSING_COMPLETED); + sendBroadcast(i); + } + private void dispatchSpeechCompletedCallbacks(String mark) { Log.i("TTS callback", "dispatch started"); // Broadcast to all clients the new value. @@ -440,6 +498,33 @@ public class TtsService extends Service implements OnCompletionListener { Log.i("TTS callback", "dispatch completed to " + N); } + private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){ + if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){ + return currentSpeechItem; + } else { + ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>(); + int start = 0; + int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; + String splitText; + SpeechItem splitItem; + while (end < currentSpeechItem.mText.length()){ + splitText = currentSpeechItem.mText.substring(start, end); + splitItem = new SpeechItem(splitText, null, SpeechItem.TEXT); + splitItems.add(splitItem); + start = end; + end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; + } + splitText = currentSpeechItem.mText.substring(start); + splitItem = new SpeechItem(splitText, null, SpeechItem.TEXT); + splitItems.add(splitItem); + mSpeechQueue.remove(0); + for (int i = splitItems.size() - 1; i >= 0; i--){ + mSpeechQueue.add(0, splitItems.get(i)); + } + return mSpeechQueue.get(0); + } + } + private void processSpeechQueue() { boolean speechQueueAvailable = false; try { @@ -449,11 +534,7 @@ public class TtsService extends Service implements OnCompletionListener { } if (mSpeechQueue.size() < 1) { mIsSpeaking = false; - // Dispatch a completion here as this is the - // only place where speech completes normally. - // Nothing left to say in the queue is a special case - // that is always a "mark" - associated text is null. - dispatchSpeechCompletedCallbacks(""); + broadcastTtsQueueProcessingCompleted(); return; } @@ -464,11 +545,12 @@ public class TtsService extends Service implements OnCompletionListener { // processSpeechQueue to continue running the queue Log.i("TTS processing: ", currentSpeechItem.mText); if (sr == null) { - if (currentSpeechItem.mType == SpeechItem.SPEECH) { - // TODO: Split text up into smaller chunks before accepting - // them for processing. + if (currentSpeechItem.mType == SpeechItem.TEXT) { + currentSpeechItem = splitCurrentTextIfNeeded(currentSpeechItem); speakInternalOnly(currentSpeechItem.mText, currentSpeechItem.mParams); + } else if (currentSpeechItem.mType == SpeechItem.IPA) { + // TODO Implement IPA support } else { // This is either silence or an earcon that was missing silence(currentSpeechItem.mDuration); @@ -534,8 +616,7 @@ public class TtsService extends Service implements OnCompletionListener { } /** - * Synthesizes the given text using the specified queuing mode and - * parameters. + * Synthesizes the given text to a file using the specified parameters. * * @param text * The String of text that should be synthesized @@ -564,8 +645,7 @@ public class TtsService extends Service implements OnCompletionListener { return false; } // Don't allow a filename that is too long - // TODO use platform constant - if (filename.length() > 250) { + if (filename.length() > MAX_FILENAME_LENGTH) { return false; } nativeSynth.synthesizeToFile(text, filename); @@ -580,6 +660,51 @@ public class TtsService extends Service implements OnCompletionListener { return true; } + /** + * Synthesizes the given IPA text to a file using the specified parameters. + * + * @param ipaText + * The String of IPA text that should be synthesized + * @param params + * An ArrayList of parameters. The first element of this array + * controls the type of voice to use. + * @param filename + * The string that gives the full output filename; it should be + * something like "/sdcard/myappsounds/mysound.wav". + * @return A boolean that indicates if the synthesis succeeded + */ + private boolean synthesizeIpaToFile(String ipaText, ArrayList<String> params, + String filename, boolean calledFromApi) { + // Only stop everything if this is a call made by an outside app trying + // to + // use the API. Do NOT stop if this is a call from within the service as + // clearing the speech queue here would be a mistake. + if (calledFromApi) { + stop(); + } + Log.i("TTS", "Synthesizing IPA to " + filename); + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + return false; + } + // Don't allow a filename that is too long + if (filename.length() > MAX_FILENAME_LENGTH) { + return false; + } + // TODO: Add nativeSynth.synthesizeIpaToFile(text, filename); + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + Log.i("TTS", "Completed synthesis for " + filename); + return true; + } + @Override public IBinder onBind(Intent intent) { if (ACTION.equals(intent.getAction())) { @@ -626,6 +751,27 @@ public class TtsService extends Service implements OnCompletionListener { } /** + * Speaks the given IPA text using the specified queueing mode and + * parameters. + * + * @param ipaText + * The IPA text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + */ + public void speakIpa(String ipaText, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.speakIpa(ipaText, queueMode, speakingParams); + } + + /** * Plays the earcon using the specified queueing mode and parameters. * * @param earcon @@ -757,6 +903,30 @@ public class TtsService extends Service implements OnCompletionListener { } /** + * Returns the level of support for the specified language. + * + * @param lang the three letter ISO language code. + * @param country the three letter ISO country code. + * @param variant the variant code associated with the country and language pair. + * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE, + * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE as defined in + * android.speech.tts.TextToSpeech. + */ + public int isLanguageAvailable(String lang, String country, String variant) { + return mSelf.isLanguageAvailable(lang, country, variant); + } + + /** + * Returns the currently set language / country / variant strings representing the + * language used by the TTS engine. + * @return null is no language is set, or an array of 3 string containing respectively + * the language, country and variant. + */ + public String[] getLanguage() { + return mSelf.getLanguage(); + } + + /** * Sets the speech rate for the TTS, which affects the synthesized voice. * * @param lang the three letter ISO language code. @@ -768,7 +938,7 @@ public class TtsService extends Service implements OnCompletionListener { } /** - * Speaks the given text using the specified queueing mode and + * Synthesizes the given text to a file using the specified * parameters. * * @param text @@ -789,6 +959,29 @@ public class TtsService extends Service implements OnCompletionListener { } return mSelf.synthesizeToFile(text, speakingParams, filename, true); } + + /** + * Synthesizes the given IPA text to a file using the specified + * parameters. + * + * @param ipaText + * The String of IPA text that should be synthesized + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + * @param filename + * The string that gives the full output filename; it should + * be something like "/sdcard/myappsounds/mysound.wav". + * @return A boolean that indicates if the synthesis succeeded + */ + public boolean synthesizeIpaToFile(String ipaText, String[] params, + String filename) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + return mSelf.synthesizeIpaToFile(ipaText, speakingParams, filename, true); + } }; } diff --git a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java index a12db8c..2ad218f 100644 --- a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java +++ b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java index 8358c5c..877fa6b 100644 --- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java +++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. @@ -25,8 +25,7 @@ import java.io.IOException; * The service that manages the L2TP-over-IPSec VPN connection. */ class L2tpIpsecService extends VpnService<L2tpIpsecProfile> { - private static final String IPSEC_SERVICE = "racoon"; - private static final String L2TP_SERVICE = "mtpd"; + private static final String IPSEC_DAEMON = "racoon"; @Override protected void connect(String serverIp, String username, String password) @@ -34,7 +33,7 @@ class L2tpIpsecService extends VpnService<L2tpIpsecProfile> { String hostIp = getHostIp(); // IPSEC - AndroidServiceProxy ipsecService = startService(IPSEC_SERVICE); + AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON); ipsecService.sendCommand( String.format("SETKEY %s %s", hostIp, serverIp)); ipsecService.sendCommand(String.format("SET_CERTS %s %s %s %s", @@ -42,11 +41,11 @@ class L2tpIpsecService extends VpnService<L2tpIpsecProfile> { getUserkeyPath())); // L2TP - AndroidServiceProxy l2tpService = startService(L2TP_SERVICE); - l2tpService.sendCommand2("l2tp", serverIp, "1701", "", - "file", getPppOptionFilePath(), - "name", username, - "password", password); + L2tpIpsecProfile p = getProfile(); + MtpdHelper.sendCommand(this, L2tpService.L2TP_DAEMON, serverIp, + L2tpService.L2TP_PORT, + (p.isSecretEnabled() ? p.getSecretString() : null), + username, password); } private String getCaCertPath() { diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpService.java index 9aad7a1..9273f35 100644 --- a/packages/VpnServices/src/com/android/server/vpn/L2tpService.java +++ b/packages/VpnServices/src/com/android/server/vpn/L2tpService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. @@ -24,19 +24,15 @@ import java.io.IOException; * The service that manages the L2TP VPN connection. */ class L2tpService extends VpnService<L2tpProfile> { - private static final String L2TP_SERVICE = "mtpd"; + static final String L2TP_DAEMON = "l2tp"; + static final String L2TP_PORT = "1701"; @Override protected void connect(String serverIp, String username, String password) throws IOException { - String hostIp = getHostIp(); - - // L2TP - AndroidServiceProxy l2tpService = startService(L2TP_SERVICE); - l2tpService.sendCommand2("l2tp", serverIp, "1701", "", - "file", getPppOptionFilePath(), - "name", username, - "password", password); + L2tpProfile p = getProfile(); + MtpdHelper.sendCommand(this, L2TP_DAEMON, serverIp, L2TP_PORT, + (p.isSecretEnabled() ? p.getSecretString() : null), + username, password); } - } diff --git a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java new file mode 100644 index 0000000..6160900 --- /dev/null +++ b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java @@ -0,0 +1,60 @@ +/* + * 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.server.vpn; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A helper class for sending commands to the MTP daemon (mtpd). + */ +class MtpdHelper { + private static final String MTPD_SERVICE = "mtpd"; + private static final String VPN_LINKNAME = "vpn"; + private static final String PPP_ARGS_SEPARATOR = ""; + + static void sendCommand(VpnService<?> vpnService, String protocol, + String serverIp, String port, String secret, String username, + String password) throws IOException { + ArrayList<String> args = new ArrayList<String>(); + args.addAll(Arrays.asList(protocol, serverIp, port)); + if (secret != null) args.add(secret); + args.add(PPP_ARGS_SEPARATOR); + addPppArguments(vpnService, args, serverIp, username, password); + + AndroidServiceProxy mtpd = vpnService.startService(MTPD_SERVICE); + mtpd.sendCommand2(args.toArray(new String[args.size()])); + } + + private static void addPppArguments(VpnService<?> vpnService, + ArrayList<String> args, String serverIp, String username, + String password) throws IOException { + args.addAll(Arrays.asList( + "linkname", VPN_LINKNAME, + "name", username, + "password", password, + "ipparam", serverIp + "@" + vpnService.getGatewayIp(), + "refuse-eap", "nodefaultroute", "usepeerdns", + "idle", "1800", + "mtu", "1400", + "mru", "1400")); + } + + private MtpdHelper() { + } +} diff --git a/packages/VpnServices/src/com/android/server/vpn/NormalProcessProxy.java b/packages/VpnServices/src/com/android/server/vpn/NormalProcessProxy.java index f20d85f..f0bbc34 100644 --- a/packages/VpnServices/src/com/android/server/vpn/NormalProcessProxy.java +++ b/packages/VpnServices/src/com/android/server/vpn/NormalProcessProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/packages/VpnServices/src/com/android/server/vpn/PptpService.java b/packages/VpnServices/src/com/android/server/vpn/PptpService.java new file mode 100644 index 0000000..01362a5 --- /dev/null +++ b/packages/VpnServices/src/com/android/server/vpn/PptpService.java @@ -0,0 +1,36 @@ +/* + * 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.server.vpn; + +import android.net.vpn.PptpProfile; + +import java.io.IOException; + +/** + * The service that manages the PPTP VPN connection. + */ +class PptpService extends VpnService<PptpProfile> { + static final String PPTP_DAEMON = "pptp"; + static final String PPTP_PORT = "1723"; + @Override + protected void connect(String serverIp, String username, String password) + throws IOException { + MtpdHelper.sendCommand(this, PPTP_DAEMON, serverIp, PPTP_PORT, null, + username, password); + } + +} diff --git a/packages/VpnServices/src/com/android/server/vpn/ProcessProxy.java b/packages/VpnServices/src/com/android/server/vpn/ProcessProxy.java index 14d7521..50fbf4b 100644 --- a/packages/VpnServices/src/com/android/server/vpn/ProcessProxy.java +++ b/packages/VpnServices/src/com/android/server/vpn/ProcessProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java index fdefe9c..44127ff 100644 --- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java +++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. @@ -20,6 +20,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; +import android.net.NetworkUtils; import android.net.vpn.VpnManager; import android.net.vpn.VpnProfile; import android.net.vpn.VpnState; @@ -31,8 +32,10 @@ import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.NetworkInterface; import java.net.Socket; import java.util.ArrayList; +import java.util.Enumeration; import java.util.List; /** @@ -153,16 +156,25 @@ abstract class VpnService<E extends VpnProfile> { } /** - * Returns the IP of the specified host name. + * Returns the IP address of the specified host name. */ protected String getIp(String hostName) throws IOException { - InetAddress iaddr = InetAddress.getByName(hostName); - byte[] aa = iaddr.getAddress(); - StringBuilder sb = new StringBuilder().append(byteToInt(aa[0])); - for (int i = 1; i < aa.length; i++) { - sb.append(".").append(byteToInt(aa[i])); + return InetAddress.getByName(hostName).getHostAddress(); + } + + /** + * Returns the IP address of the default gateway. + */ + protected String getGatewayIp() throws IOException { + Enumeration<NetworkInterface> ifces = + NetworkInterface.getNetworkInterfaces(); + for (; ifces.hasMoreElements(); ) { + NetworkInterface ni = ifces.nextElement(); + int gateway = NetworkUtils.getDefaultRoute(ni.getName()); + if (gateway == 0) continue; + return toInetAddress(gateway).getHostAddress(); } - return sb.toString(); + throw new IOException("Default gateway is not available"); } /** @@ -170,7 +182,7 @@ abstract class VpnService<E extends VpnProfile> { * connection is established. */ protected String getConnectMonitorFile() { - return "/etc/ppp/ip-up"; + return "/etc/ppp/ip-up-vpn"; } /** @@ -461,12 +473,18 @@ abstract class VpnService<E extends VpnProfile> { } private String reallyGetHostIp() throws IOException { - Socket s = new Socket(); - s.connect(new InetSocketAddress("www.google.com", 80), DNS_TIMEOUT); - String ipAddress = s.getLocalAddress().getHostAddress(); - Log.d(TAG, "Host IP: " + ipAddress); - s.close(); - return ipAddress; + Enumeration<NetworkInterface> ifces = + NetworkInterface.getNetworkInterfaces(); + for (; ifces.hasMoreElements(); ) { + NetworkInterface ni = ifces.nextElement(); + int gateway = NetworkUtils.getDefaultRoute(ni.getName()); + if (gateway == 0) continue; + Enumeration<InetAddress> addrs = ni.getInetAddresses(); + for (; addrs.hasMoreElements(); ) { + return addrs.nextElement().getHostAddress(); + } + } + throw new IOException("Host IP is not available"); } private String getProfileSubpath(String subpath) throws IOException { @@ -496,8 +514,13 @@ abstract class VpnService<E extends VpnProfile> { return ((message == null) || (message.length() == 0)); } - private static int byteToInt(byte b) { - return ((int) b) & 0x0FF; + private InetAddress toInetAddress(int addr) throws IOException { + byte[] aa = new byte[4]; + for (int i= 0; i < aa.length; i++) { + aa[i] = (byte) (addr & 0x0FF); + addr >>= 8; + } + return InetAddress.getByAddress(aa); } private class ServiceHelper implements ProcessProxy.Callback { diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java index 7195ea6..63fc858 100644 --- a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java +++ b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index fdd4617..c67f0b5 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -34,7 +34,6 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -43,12 +42,13 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.Log; import android.util.SparseArray; import android.backup.IBackupManager; +import android.backup.IRestoreObserver; import android.backup.IRestoreSession; -import android.backup.BackupManager; import android.backup.RestoreSet; import com.android.internal.backup.LocalTransport; @@ -60,7 +60,6 @@ import com.android.server.PackageManagerBackupAgent.Metadata; import java.io.EOFException; import java.io.File; import java.io.FileDescriptor; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -68,16 +67,19 @@ import java.lang.String; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; +import java.util.Map; class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "BackupManagerService"; private static final boolean DEBUG = true; + // Persistent properties + private static final String BACKUP_TRANSPORT_PROPERTY = "persist.service.bkup.trans"; + private static final String BACKUP_ENABLED_PROPERTY = "persist.service.bkup.enabled"; + // Default time to wait after data changes before we back up the data - private static final long COLLECTION_INTERVAL = 1000; - //private static final long COLLECTION_INTERVAL = 3 * 60 * 1000; + private static final long COLLECTION_INTERVAL = 3 * 60 * 1000; private static final int MSG_RUN_BACKUP = 1; private static final int MSG_RUN_FULL_BACKUP = 2; @@ -88,10 +90,11 @@ class BackupManagerService extends IBackupManager.Stub { private Context mContext; private PackageManager mPackageManager; - private final IActivityManager mActivityManager; + private IActivityManager mActivityManager; + private boolean mEnabled; // access to this is synchronized on 'this' private final BackupHandler mBackupHandler = new BackupHandler(); // map UIDs to the set of backup client services within that UID's app set - private SparseArray<HashSet<ApplicationInfo>> mBackupParticipants + private final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants = new SparseArray<HashSet<ApplicationInfo>>(); // set of backup services that have pending changes private class BackupRequest { @@ -110,8 +113,8 @@ class BackupManagerService extends IBackupManager.Stub { // Backups that we haven't started yet. private HashMap<ApplicationInfo,BackupRequest> mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>(); - // Do we need to back up the package manager metadata on the next pass? - private boolean mDoPackageManager; + + // Pseudoname that we use for the Package Manager metadata "package" private static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; // locking around the pending-backup management @@ -129,12 +132,27 @@ class BackupManagerService extends IBackupManager.Stub { private final Object mClearDataLock = new Object(); private volatile boolean mClearingData; - // Current active transport & restore session - private int mTransportId; + // Transport bookkeeping + private final HashMap<String,IBackupTransport> mTransports + = new HashMap<String,IBackupTransport>(); + private String mCurrentTransport; private IBackupTransport mLocalTransport, mGoogleTransport; private RestoreSession mActiveRestoreSession; - private File mStateDir; + private class RestoreParams { + public IBackupTransport transport; + public IRestoreObserver observer; + public long token; + + RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token) { + transport = _transport; + observer = _obs; + token = _token; + } + } + + // Where we keep our journal files and other bookkeeping + private File mBaseStateDir; private File mDataDir; private File mJournalDir; private File mJournal; @@ -146,13 +164,15 @@ class BackupManagerService extends IBackupManager.Stub { mActivityManager = ActivityManagerNative.getDefault(); // Set up our bookkeeping - mStateDir = new File(Environment.getDataDirectory(), "backup"); - mStateDir.mkdirs(); + // !!! STOPSHIP: make this disabled by default so that we then gate on + // setupwizard or other opt-out UI + mEnabled = SystemProperties.getBoolean(BACKUP_ENABLED_PROPERTY, true); + mBaseStateDir = new File(Environment.getDataDirectory(), "backup"); mDataDir = Environment.getDownloadCacheDirectory(); // Set up the backup-request journaling - mJournalDir = new File(mStateDir, "pending"); - mJournalDir.mkdirs(); + mJournalDir = new File(mBaseStateDir, "pending"); + mJournalDir.mkdirs(); // creates mBaseStateDir along the way makeJournalLocked(); // okay because no other threads are running yet // Build our mapping of uid to backup client services. This implicitly @@ -165,12 +185,19 @@ class BackupManagerService extends IBackupManager.Stub { // Set up our transport options and initialize the default transport // TODO: Have transports register themselves somehow? // TODO: Don't create transports that we don't need to? - mTransportId = BackupManager.TRANSPORT_LOCAL; - //mTransportId = BackupManager.TRANSPORT_GOOGLE; mLocalTransport = new LocalTransport(context); // This is actually pretty cheap - mGoogleTransport = null; + ComponentName localName = new ComponentName(context, LocalTransport.class); + registerTransport(localName.flattenToShortString(), mLocalTransport); - // Attach to the Google backup transport. + mGoogleTransport = null; + // !!! TODO: set up the default transport name "the right way" + mCurrentTransport = SystemProperties.get(BACKUP_TRANSPORT_PROPERTY, + "com.google.android.backup/.BackupTransportService"); + if (DEBUG) Log.v(TAG, "Starting with transport " + mCurrentTransport); + + // Attach to the Google backup transport. When this comes up, it will set + // itself as the current transport because we explicitly reset mCurrentTransport + // to null. Intent intent = new Intent().setComponent(new ComponentName( "com.google.android.backup", "com.google.android.backup.BackupTransportService")); @@ -229,6 +256,13 @@ class BackupManagerService extends IBackupManager.Stub { } } + // Add a transport to our set of available backends + private void registerTransport(String name, IBackupTransport transport) { + synchronized (mTransports) { + mTransports.put(name, transport); + } + } + // ----- Track installation/removal of packages ----- BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { @@ -274,11 +308,13 @@ class BackupManagerService extends IBackupManager.Stub { public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) Log.v(TAG, "Connected to Google transport"); mGoogleTransport = IBackupTransport.Stub.asInterface(service); + registerTransport(name.flattenToShortString(), mGoogleTransport); } public void onServiceDisconnected(ComponentName name) { if (DEBUG) Log.v(TAG, "Disconnected from Google transport"); mGoogleTransport = null; + registerTransport(name.flattenToShortString(), null); } }; @@ -290,7 +326,7 @@ class BackupManagerService extends IBackupManager.Stub { switch (msg.what) { case MSG_RUN_BACKUP: { - IBackupTransport transport = getTransport(mTransportId); + IBackupTransport transport = getTransport(mCurrentTransport); if (transport == null) { Log.v(TAG, "Backup requested but no transport available"); break; @@ -337,9 +373,8 @@ class BackupManagerService extends IBackupManager.Stub { case MSG_RUN_RESTORE: { - int token = msg.arg1; - IBackupTransport transport = (IBackupTransport)msg.obj; - (new PerformRestoreThread(transport, token)).start(); + RestoreParams params = (RestoreParams)msg.obj; + (new PerformRestoreThread(params.transport, params.observer, params.token)).start(); break; } } @@ -360,7 +395,8 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) { Log.v(TAG, "Adding " + targetPkgs.size() + " backup participants:"); for (PackageInfo p : targetPkgs) { - Log.v(TAG, " " + p + " agent=" + p.applicationInfo.backupAgentName); + Log.v(TAG, " " + p + " agent=" + p.applicationInfo.backupAgentName + + " uid=" + p.applicationInfo.uid); } } @@ -373,7 +409,6 @@ class BackupManagerService extends IBackupManager.Stub { mBackupParticipants.put(uid, set); } set.add(pkg.applicationInfo); - backUpPackageManagerData(); } } } @@ -417,7 +452,6 @@ class BackupManagerService extends IBackupManager.Stub { for (ApplicationInfo entry: set) { if (entry.packageName.equals(pkg.packageName)) { set.remove(entry); - backUpPackageManagerData(); break; } } @@ -460,34 +494,27 @@ class BackupManagerService extends IBackupManager.Stub { addPackageParticipantsLockedInner(packageName, allApps); } - private void backUpPackageManagerData() { - // No need to schedule a backup just for the metadata; just piggyback on - // the next actual data backup. - synchronized(this) { - mDoPackageManager = true; - } - } - // The queue lock should be held when scheduling a backup pass private void scheduleBackupPassLocked(long timeFromNowMillis) { - mBackupHandler.removeMessages(MSG_RUN_BACKUP); - mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, timeFromNowMillis); + // We only schedule backups when we're actually enabled + synchronized (this) { + if (mEnabled) { + mBackupHandler.removeMessages(MSG_RUN_BACKUP); + mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, timeFromNowMillis); + } else if (DEBUG) { + Log.v(TAG, "Disabled, so not scheduling backup pass"); + } + } } // Return the given transport - private IBackupTransport getTransport(int transportID) { - switch (transportID) { - case BackupManager.TRANSPORT_LOCAL: - Log.v(TAG, "Supplying local transport"); - return mLocalTransport; - - case BackupManager.TRANSPORT_GOOGLE: - Log.v(TAG, "Supplying Google transport"); - return mGoogleTransport; - - default: - Log.e(TAG, "Asked for unknown transport " + transportID); - return null; + private IBackupTransport getTransport(String transportName) { + synchronized (mTransports) { + IBackupTransport transport = mTransports.get(transportName); + if (transport == null) { + Log.w(TAG, "Requested unavailable transport: " + transportName); + } + return transport; } } @@ -530,6 +557,19 @@ class BackupManagerService extends IBackupManager.Stub { // clear an application's data, blocking until the operation completes or times out void clearApplicationDataSynchronous(String packageName) { + // Don't wipe packages marked allowClearUserData=false + try { + PackageInfo info = mPackageManager.getPackageInfo(packageName, 0); + if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) == 0) { + if (DEBUG) Log.i(TAG, "allowClearUserData=false so not wiping " + + packageName); + return; + } + } catch (NameNotFoundException e) { + Log.w(TAG, "Tried to clear data for " + packageName + " but not found"); + return; + } + ClearDataObserver observer = new ClearDataObserver(); synchronized(mClearDataLock) { @@ -565,6 +605,7 @@ class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "PerformBackupThread"; IBackupTransport mTransport; ArrayList<BackupRequest> mQueue; + File mStateDir; File mJournal; public PerformBackupThread(IBackupTransport transport, ArrayList<BackupRequest> queue, @@ -572,32 +613,31 @@ class BackupManagerService extends IBackupManager.Stub { mTransport = transport; mQueue = queue; mJournal = journal; + + try { + mStateDir = new File(mBaseStateDir, transport.transportDirName()); + } catch (RemoteException e) { + // can't happen; the transport is local + } + mStateDir.mkdirs(); } @Override public void run() { if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets"); - // First, back up the package manager metadata if necessary - boolean doPackageManager; - synchronized (BackupManagerService.this) { - doPackageManager = mDoPackageManager; - mDoPackageManager = false; - } - if (doPackageManager) { - // The package manager doesn't have a proper <application> etc, but since - // it's running here in the system process we can just set up its agent - // directly and use a synthetic BackupRequest. - if (DEBUG) Log.i(TAG, "Running PM backup pass as well"); - - PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( - mPackageManager, allAgentPackages()); - BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); - pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; - processOneBackup(pmRequest, - IBackupAgent.Stub.asInterface(pmAgent.onBind()), - mTransport); - } + // The package manager doesn't have a proper <application> etc, but since + // it's running here in the system process we can just set up its agent + // directly and use a synthetic BackupRequest. We always run this pass + // because it's cheap and this way we guarantee that we don't get out of + // step even if we're selecting among various transports at run time. + PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( + mPackageManager, allAgentPackages()); + BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); + pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; + processOneBackup(pmRequest, + IBackupAgent.Stub.asInterface(pmAgent.onBind()), + mTransport); // Now run all the backups in our queue doQueuedBackups(mTransport); @@ -759,8 +799,10 @@ class BackupManagerService extends IBackupManager.Stub { class PerformRestoreThread extends Thread { private IBackupTransport mTransport; - private int mToken; + private IRestoreObserver mObserver; + private long mToken; private RestoreSet mImage; + private File mStateDir; class RestoreRequest { public PackageInfo app; @@ -772,9 +814,18 @@ class BackupManagerService extends IBackupManager.Stub { } } - PerformRestoreThread(IBackupTransport transport, int restoreSetToken) { + PerformRestoreThread(IBackupTransport transport, IRestoreObserver observer, + long restoreSetToken) { mTransport = transport; + mObserver = observer; mToken = restoreSetToken; + + try { + mStateDir = new File(mBaseStateDir, transport.transportDirName()); + } catch (RemoteException e) { + // can't happen; the transport is local + } + mStateDir.mkdirs(); } @Override @@ -795,6 +846,8 @@ class BackupManagerService extends IBackupManager.Stub { * 4. shut down the transport */ + int error = -1; // assume error + // build the set of apps to restore try { RestoreSet[] images = mTransport.getAvailableRestoreSets(); @@ -821,9 +874,19 @@ class BackupManagerService extends IBackupManager.Stub { List<PackageInfo> agentPackages = allAgentPackages(); restorePackages.addAll(agentPackages); - // STOPSHIP TODO: pick out the set for this token (instead of images[0]) - long token = images[0].token; - if (!mTransport.startRestore(token, restorePackages.toArray(new PackageInfo[0]))) { + // let the observer know that we're running + if (mObserver != null) { + try { + // !!! TODO: get an actual count from the transport after + // its startRestore() runs? + mObserver.restoreStarting(restorePackages.size()); + } catch (RemoteException e) { + Log.d(TAG, "Restore observer died at restoreStarting"); + mObserver = null; + } + } + + if (!mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0]))) { // STOPSHIP TODO: Handle the failure somehow? Log.e(TAG, "Error starting restore operation"); return; @@ -848,6 +911,7 @@ class BackupManagerService extends IBackupManager.Stub { mPackageManager, agentPackages); processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind())); + int count = 0; for (;;) { packageName = mTransport.nextRestorePackage(); if (packageName == null) { @@ -858,6 +922,16 @@ class BackupManagerService extends IBackupManager.Stub { break; } + if (mObserver != null) { + ++count; + try { + mObserver.onUpdate(count); + } catch (RemoteException e) { + Log.d(TAG, "Restore observer died in onUpdate"); + mObserver = null; + } + } + Metadata metaInfo = pmAgent.getRestoredMetadata(packageName); if (metaInfo == null) { Log.e(TAG, "Missing metadata for " + packageName); @@ -901,6 +975,9 @@ class BackupManagerService extends IBackupManager.Stub { mActivityManager.unbindBackupAgent(packageInfo.applicationInfo); } } + + // if we get this far, report success to the observer + error = 0; } catch (NameNotFoundException e) { // STOPSHIP TODO: Handle the failure somehow? Log.e(TAG, "Invalid paackage restoring data", e); @@ -913,6 +990,14 @@ class BackupManagerService extends IBackupManager.Stub { } catch (RemoteException e) { Log.e(TAG, "Error finishing restore", e); } + + if (mObserver != null) { + try { + mObserver.restoreFinished(error); + } catch (RemoteException e) { + Log.d(TAG, "Restore observer died at restoreFinished"); + } + } } } @@ -1016,7 +1101,7 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) { int numKeys = mPendingBackups.size(); - Log.d(TAG, "Scheduling backup for " + numKeys + " participants:"); + Log.d(TAG, "Now awaiting backup for " + numKeys + " participants:"); for (BackupRequest b : mPendingBackups.values()) { Log.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName); } @@ -1046,7 +1131,7 @@ class BackupManagerService extends IBackupManager.Stub { // Run a backup pass immediately for any applications that have declared // that they have pending updates. public void backupNow() throws RemoteException { - mContext.enforceCallingPermission("android.permission.BACKUP", "tryBackupNow"); + mContext.enforceCallingPermission("android.permission.BACKUP", "backupNow"); if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { @@ -1054,21 +1139,78 @@ class BackupManagerService extends IBackupManager.Stub { } } - // Report the currently active transport - public int getCurrentTransport() { - mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport"); - Log.v(TAG, "getCurrentTransport() returning " + mTransportId); - return mTransportId; + // Enable/disable the backup transport + public void setBackupEnabled(boolean enable) { + mContext.enforceCallingPermission("android.permission.BACKUP", "setBackupEnabled"); + + boolean wasEnabled = mEnabled; + synchronized (this) { + SystemProperties.set(BACKUP_ENABLED_PROPERTY, enable ? "true" : "false"); + mEnabled = enable; + } + + if (enable && !wasEnabled) { + synchronized (mQueueLock) { + if (mPendingBackups.size() > 0) { + // !!! TODO: better policy around timing of the first backup pass + if (DEBUG) Log.v(TAG, "Backup enabled with pending data changes, scheduling"); + this.scheduleBackupPassLocked(COLLECTION_INTERVAL); + } + } + } +} + + // Report whether the backup mechanism is currently enabled + public boolean isBackupEnabled() { + mContext.enforceCallingPermission("android.permission.BACKUP", "isBackupEnabled"); + return mEnabled; // no need to synchronize just to read it + } + + // Report the name of the currently active transport + public String getCurrentTransport() { + mContext.enforceCallingPermission("android.permission.BACKUP", "getCurrentTransport"); + Log.v(TAG, "getCurrentTransport() returning " + mCurrentTransport); + return mCurrentTransport; + } + + // Report all known, available backup transports + public String[] listAllTransports() { + mContext.enforceCallingPermission("android.permission.BACKUP", "listAllTransports"); + + String[] list = null; + ArrayList<String> known = new ArrayList<String>(); + for (Map.Entry<String, IBackupTransport> entry : mTransports.entrySet()) { + if (entry.getValue() != null) { + known.add(entry.getKey()); + } + } + + if (known.size() > 0) { + list = new String[known.size()]; + known.toArray(list); + } + return list; } - // Select which transport to use for the next backup operation - public int selectBackupTransport(int transportId) { + // Select which transport to use for the next backup operation. If the given + // name is not one of the available transports, no action is taken and the method + // returns null. + public String selectBackupTransport(String transport) { mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport"); - int prevTransport = mTransportId; - mTransportId = transportId; - Log.v(TAG, "selectBackupTransport() set " + mTransportId + " returning " + prevTransport); - return prevTransport; + synchronized (mTransports) { + String prevTransport = null; + if (mTransports.get(transport) != null) { + prevTransport = mCurrentTransport; + mCurrentTransport = transport; + SystemProperties.set(BACKUP_TRANSPORT_PROPERTY, transport); + Log.v(TAG, "selectBackupTransport() set " + mCurrentTransport + + " returning " + prevTransport); + } else { + Log.w(TAG, "Attempt to select unavailable transport " + transport); + } + return prevTransport; + } } // Callback: a requested backup agent has been instantiated. This should only @@ -1106,7 +1248,7 @@ class BackupManagerService extends IBackupManager.Stub { } // Hand off a restore session - public IRestoreSession beginRestoreSession(int transportID) { + public IRestoreSession beginRestoreSession(String transport) { mContext.enforceCallingPermission("android.permission.BACKUP", "beginRestoreSession"); synchronized(this) { @@ -1114,7 +1256,7 @@ class BackupManagerService extends IBackupManager.Stub { Log.d(TAG, "Restore session requested but one already active"); return null; } - mActiveRestoreSession = new RestoreSession(transportID); + mActiveRestoreSession = new RestoreSession(transport); } return mActiveRestoreSession; } @@ -1127,8 +1269,8 @@ class BackupManagerService extends IBackupManager.Stub { private IBackupTransport mRestoreTransport = null; RestoreSet[] mRestoreSets = null; - RestoreSession(int transportID) { - mRestoreTransport = getTransport(transportID); + RestoreSession(String transport) { + mRestoreTransport = getTransport(transport); } // --- Binder interface --- @@ -1150,15 +1292,15 @@ class BackupManagerService extends IBackupManager.Stub { } } - public int performRestore(int token) throws android.os.RemoteException { + public int performRestore(long token, IRestoreObserver observer) + throws android.os.RemoteException { mContext.enforceCallingPermission("android.permission.BACKUP", "performRestore"); if (mRestoreSets != null) { for (int i = 0; i < mRestoreSets.length; i++) { if (token == mRestoreSets[i].token) { - Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE, - mRestoreTransport); - msg.arg1 = token; + Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); + msg.obj = new RestoreParams(mRestoreTransport, observer, token); mBackupHandler.sendMessage(msg); return 0; } @@ -1189,6 +1331,11 @@ class BackupManagerService extends IBackupManager.Stub { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mQueueLock) { + pw.println("Available transports:"); + for (String t : listAllTransports()) { + String pad = (t.equals(mCurrentTransport)) ? " * " : " "; + pw.println(pad + t); + } int N = mBackupParticipants.size(); pw.println("Participants:"); for (int i=0; i<N; i++) { @@ -1197,14 +1344,12 @@ class BackupManagerService extends IBackupManager.Stub { pw.println(uid); HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); for (ApplicationInfo app: participants) { - pw.print(" "); - pw.println(app.toString()); + pw.println(" " + app.toString()); } } pw.println("Pending: " + mPendingBackups.size()); for (BackupRequest req : mPendingBackups.values()) { - pw.print(" "); - pw.println(req); + pw.println(" " + req); } } } diff --git a/services/java/com/android/server/PackageManagerBackupAgent.java b/services/java/com/android/server/PackageManagerBackupAgent.java index 66fb86d..a1b4c26 100644 --- a/services/java/com/android/server/PackageManagerBackupAgent.java +++ b/services/java/com/android/server/PackageManagerBackupAgent.java @@ -59,7 +59,14 @@ public class PackageManagerBackupAgent extends BackupAgent { private List<PackageInfo> mAllPackages; private PackageManager mPackageManager; + // version & signature info of each app in a restore set private HashMap<String, Metadata> mRestoredSignatures; + // The version info of each backed-up app as read from the state file + private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>(); + + private final HashSet<String> mExisting = new HashSet<String>(); + private int mStoredSdkVersion; + private String mStoredIncrementalVersion; public class Metadata { public int versionCode; @@ -96,35 +103,78 @@ public class PackageManagerBackupAgent extends BackupAgent { ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); // we'll reuse these DataOutputStream outWriter = new DataOutputStream(bufStream); - HashSet<String> existing = parseStateFile(oldState); + parseStateFile(oldState); + + // If the stored version string differs, we need to re-backup all + // of the metadata. We force this by removing everything from the + // "already backed up" map built by parseStateFile(). + if (mStoredIncrementalVersion == null + || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) { + Log.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs " + + Build.VERSION.INCREMENTAL + " - rewriting"); + mExisting.clear(); + } try { /* * Global metadata: * - * int version -- the SDK version of the OS itself on the device - * that produced this backup set. Used to reject - * backups from later OSes onto earlier ones. + * int SDKversion -- the SDK version of the OS itself on the device + * that produced this backup set. Used to reject + * backups from later OSes onto earlier ones. + * String incremental -- the incremental release name of the OS stored in + * the backup set. */ - if (!existing.contains(GLOBAL_METADATA_KEY)) { + if (!mExisting.contains(GLOBAL_METADATA_KEY)) { if (DEBUG) Log.v(TAG, "Storing global metadata key"); outWriter.writeInt(Build.VERSION.SDK_INT); + outWriter.writeUTF(Build.VERSION.INCREMENTAL); byte[] metadata = bufStream.toByteArray(); data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length); data.writeEntityData(metadata, metadata.length); } else { if (DEBUG) Log.v(TAG, "Global metadata key already stored"); + // don't consider it to have been skipped/deleted + mExisting.remove(GLOBAL_METADATA_KEY); } // For each app we have on device, see if we've backed it up yet. If not, // write its signature block to the output, keyed on the package name. for (PackageInfo pkg : mAllPackages) { String packName = pkg.packageName; - if (!existing.contains(packName)) { - // We haven't stored this app's signatures yet, so we do that now + if (packName.equals(GLOBAL_METADATA_KEY)) { + // We've already handled the metadata key; skip it here + continue; + } else { + PackageInfo info = null; try { - PackageInfo info = mPackageManager.getPackageInfo(packName, + info = mPackageManager.getPackageInfo(packName, PackageManager.GET_SIGNATURES); + } catch (NameNotFoundException e) { + // Weird; we just found it, and now are told it doesn't exist. + // Treat it as having been removed from the device. + mExisting.add(packName); + continue; + } + + boolean doBackup = false; + if (!mExisting.contains(packName)) { + // We haven't backed up this app before + doBackup = true; + } else { + // We *have* backed this one up before. Check whether the version + // of the backup matches the version of the current app; if they + // don't match, the app has been updated and we need to store its + // metadata again. In either case, take it out of mExisting so that + // we don't consider it deleted later. + if (info.versionCode != mStateVersions.get(packName).versionCode) { + doBackup = true; + } + mExisting.remove(packName); + } + + if (doBackup) { + // We need to store this app's metadata /* * Metadata for each package: * @@ -132,7 +182,7 @@ public class PackageManagerBackupAgent extends BackupAgent { * byte[] signatures -- [len] flattened Signature[] of the package */ - // marshall the version code in a canonical form + // marshal the version code in a canonical form bufStream.reset(); outWriter.writeInt(info.versionCode); byte[] versionBuf = bufStream.toByteArray(); @@ -150,18 +200,6 @@ public class PackageManagerBackupAgent extends BackupAgent { data.writeEntityHeader(packName, versionBuf.length + sigs.length); data.writeEntityData(versionBuf, versionBuf.length); data.writeEntityData(sigs, sigs.length); - } catch (NameNotFoundException e) { - // Weird; we just found it, and now are told it doesn't exist. - // Treat it as having been removed from the device. - existing.add(packName); - } - } else { - // We've already backed up this app. Remove it from the set so - // we can tell at the end what has disappeared from the device. - // !!! TODO: take out the debugging message - if (DEBUG) Log.v(TAG, "= already backed up metadata for " + packName); - if (!existing.remove(packName)) { - Log.d(TAG, "*** failed to remove " + packName + " from package set!"); } } } @@ -169,7 +207,7 @@ public class PackageManagerBackupAgent extends BackupAgent { // At this point, the only entries in 'existing' are apps that were // mentioned in the saved state file, but appear to no longer be present // on the device. Write a deletion entity for them. - for (String app : existing) { + for (String app : mExisting) { // !!! TODO: take out this msg if (DEBUG) Log.v(TAG, "- removing metadata for deleted pkg " + app); try { @@ -212,17 +250,21 @@ public class PackageManagerBackupAgent extends BackupAgent { DataInputStream in = new DataInputStream(baStream); if (key.equals(GLOBAL_METADATA_KEY)) { - storedSystemVersion = in.readInt(); + int storedSdkVersion = in.readInt(); if (DEBUG) Log.v(TAG, " storedSystemVersion = " + storedSystemVersion); if (storedSystemVersion > Build.VERSION.SDK_INT) { // returning before setting the sig map means we rejected the restore set Log.w(TAG, "Restore set was from a later version of Android; not restoring"); return; } + mStoredSdkVersion = storedSdkVersion; + mStoredIncrementalVersion = in.readUTF(); // !!! TODO: remove this debugging output if (DEBUG) { Log.i(TAG, "Restore set version " + storedSystemVersion - + " is compatible with OS version " + Build.VERSION.SDK_INT); + + " is compatible with OS version " + Build.VERSION.SDK_INT + + " (" + mStoredIncrementalVersion + " vs " + + Build.VERSION.INCREMENTAL + ")"); } } else { // it's a file metadata record @@ -299,31 +341,45 @@ public class PackageManagerBackupAgent extends BackupAgent { } // Util: parse out an existing state file into a usable structure - private HashSet<String> parseStateFile(ParcelFileDescriptor stateFile) { - HashSet<String> set = new HashSet<String>(); + private void parseStateFile(ParcelFileDescriptor stateFile) { + mExisting.clear(); + mStateVersions.clear(); + mStoredSdkVersion = 0; + mStoredIncrementalVersion = null; + // The state file is just the list of app names we have stored signatures for + // with the exception of the metadata block, to which is also appended the + // version numbers corresponding with the last time we wrote this PM block. + // If they mismatch the current system, we'll re-store the metadata key. FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor()); DataInputStream in = new DataInputStream(instream); int bufSize = 256; byte[] buf = new byte[bufSize]; try { - int nameSize = in.readInt(); - if (bufSize < nameSize) { - bufSize = nameSize + 32; - buf = new byte[bufSize]; + String pkg = in.readUTF(); + if (pkg.equals(GLOBAL_METADATA_KEY)) { + mStoredSdkVersion = in.readInt(); + mStoredIncrementalVersion = in.readUTF(); + mExisting.add(GLOBAL_METADATA_KEY); + } else { + Log.e(TAG, "No global metadata in state file!"); + return; + } + + // The global metadata was first; now read all the apps + while (true) { + pkg = in.readUTF(); + int versionCode = in.readInt(); + mExisting.add(pkg); + mStateVersions.put(pkg, new Metadata(versionCode, null)); } - in.read(buf, 0, nameSize); - String pkg = new String(buf, 0, nameSize); - set.add(pkg); } catch (EOFException eof) { // safe; we're done } catch (IOException e) { // whoops, bad state file. abort. - Log.e(TAG, "Unable to read Package Manager state file"); - return null; + Log.e(TAG, "Unable to read Package Manager state file: " + e); } - return set; } // Util: write out our new backup state file @@ -333,15 +389,14 @@ public class PackageManagerBackupAgent extends BackupAgent { try { // by the time we get here we know we've stored the global metadata record - byte[] metaNameBuf = GLOBAL_METADATA_KEY.getBytes(); - out.writeInt(metaNameBuf.length); - out.write(metaNameBuf); + out.writeUTF(GLOBAL_METADATA_KEY); + out.writeInt(Build.VERSION.SDK_INT); + out.writeUTF(Build.VERSION.INCREMENTAL); // now write all the app names too for (PackageInfo pkg : pkgs) { - byte[] pkgNameBuf = pkg.packageName.getBytes(); - out.writeInt(pkgNameBuf.length); - out.write(pkgNameBuf); + out.writeUTF(pkg.packageName); + out.writeInt(pkg.versionCode); } } catch (IOException e) { Log.e(TAG, "Unable to write package manager state file!"); diff --git a/services/java/com/android/server/WallpaperService.java b/services/java/com/android/server/WallpaperService.java index 5532894..d921baf 100644 --- a/services/java/com/android/server/WallpaperService.java +++ b/services/java/com/android/server/WallpaperService.java @@ -18,8 +18,10 @@ package com.android.server; import static android.os.FileObserver.*; import static android.os.ParcelFileDescriptor.*; + import android.app.IWallpaperService; import android.app.IWallpaperServiceCallback; +import android.backup.BackupManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -154,7 +156,16 @@ class WallpaperService extends IWallpaperService.Stub { public ParcelFileDescriptor setWallpaper() { checkPermission(android.Manifest.permission.SET_WALLPAPER); try { - return ParcelFileDescriptor.open(WALLPAPER_FILE, MODE_CREATE|MODE_READ_WRITE); + ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE, + MODE_CREATE|MODE_READ_WRITE); + + // changing the wallpaper means we'll need to back up the new one + long origId = Binder.clearCallingIdentity(); + BackupManager bm = new BackupManager(mContext); + bm.dataChanged(); + Binder.restoreCallingIdentity(origId); + + return fd; } catch (FileNotFoundException e) { if (Config.LOGD) Log.d(TAG, "Error setting wallpaper", e); } diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index fef3598..68bf4fb 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -504,6 +504,7 @@ public class Watchdog extends Thread { if (mPhoneMemMonitor.checkLocked(curTime, mPhonePid, mPhonePss)) { // Just kill the phone process and let it restart. + Log.i(TAG, "Watchdog is killing the phone process"); Process.killProcess(mPhonePid); } } else { @@ -848,6 +849,7 @@ public class Watchdog extends Thread { // Only kill the process if the debugger is not attached. if (!Debug.isDebuggerConnected()) { + Log.i(TAG, "Watchdog is killing the system process"); Process.killProcess(Process.myPid()); } } diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 969fbfe..e91798b 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -1349,39 +1349,42 @@ public class WifiService extends IWifiManager.Stub { level = 0; } - // bssid is the hash key - scanResult = mScanResultCache.get(bssid); - if (scanResult != null) { - scanResult.level = level; - } else { - /* - * The formatting of the results returned by - * wpa_supplicant is intended to make the fields - * line up nicely when printed, - * not to make them easy to parse. So we have to - * apply some heuristics to figure out which field - * is the SSID and which field is the flags. - */ - String ssid; - String flags; - if (result.length == 4) { - if (result[3].charAt(0) == '[') { - flags = result[3]; - ssid = ""; - } else { - flags = ""; - ssid = result[3]; - } - } else if (result.length == 5) { + /* + * The formatting of the results returned by + * wpa_supplicant is intended to make the fields + * line up nicely when printed, + * not to make them easy to parse. So we have to + * apply some heuristics to figure out which field + * is the SSID and which field is the flags. + */ + String ssid; + String flags; + if (result.length == 4) { + if (result[3].charAt(0) == '[') { flags = result[3]; - ssid = result[4]; + ssid = ""; } else { - // Here, we must have 3 fields: no flags and ssid - // set flags = ""; - ssid = ""; + ssid = result[3]; } + } else if (result.length == 5) { + flags = result[3]; + ssid = result[4]; + } else { + // Here, we must have 3 fields: no flags and ssid + // set + flags = ""; + ssid = ""; + } + // bssid is the hash key + scanResult = mScanResultCache.get(bssid); + if (scanResult != null) { + scanResult.level = level; + scanResult.SSID = ssid; + scanResult.capabilities = flags; + scanResult.frequency = frequency; + } else { // Do not add scan results that have no SSID set if (0 < ssid.trim().length()) { scanResult = diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 975c8209..8c3f61e 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -78,6 +78,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.TokenWatcher; import android.provider.Settings; +import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.SparseIntArray; @@ -418,7 +419,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final Rect mTempRect = new Rect(); final Configuration mTempConfiguration = new Configuration(); - + int screenLayout = Configuration.SCREENLAYOUT_UNDEFINED; + public static WindowManagerService main(Context context, PowerManagerService pm, boolean haveInputMethods) { WMThread thr = new WMThread(context, pm, haveInputMethods); @@ -3731,6 +3733,40 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo orientation = Configuration.ORIENTATION_LANDSCAPE; } config.orientation = orientation; + + if (screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) { + // Note we only do this once because at this point we don't + // expect the screen to change in this way at runtime, and want + // to avoid all of this computation for every config change. + DisplayMetrics dm = new DisplayMetrics(); + mDisplay.getMetrics(dm); + int longSize = dw; + int shortSize = dh; + if (longSize < shortSize) { + int tmp = longSize; + longSize = shortSize; + shortSize = tmp; + } + longSize = (int)(longSize/dm.density); + shortSize = (int)(shortSize/dm.density); + + // These semi-magic numbers define our compatibility modes for + // applications with different screens. Don't change unless you + // make sure to test lots and lots of apps! + if (longSize < 470) { + // This is shorter than an HVGA normal density screen (which + // is 480 pixels on its long side). + screenLayout = Configuration.SCREENLAYOUT_SMALL; + } else if (longSize > 490 && shortSize > 330) { + // This is larger than an HVGA normal density screen (which + // is 480x320 pixels). + screenLayout = Configuration.SCREENLAYOUT_LARGE; + } else { + screenLayout = Configuration.SCREENLAYOUT_NORMAL; + } + } + config.screenLayout = screenLayout; + config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; mPolicy.adjustConfigurationLw(config); @@ -3809,7 +3845,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev, - ev, true, false); + ev, true, false, pid, uid); if (MEASURE_LATENCY) { lt.sample("3 Last dispatch finished ", System.nanoTime() - qev.whenNano); @@ -4029,7 +4065,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo TAG, "dispatchTrackball [" + ev.getAction() +"] <" + ev.getX() + ", " + ev.getY() + ">"); Object focusObj = mKeyWaiter.waitForNextEventTarget(null, qev, - ev, false, false); + ev, false, false, pid, uid); if (focusObj == null) { Log.w(TAG, "No focus window, dropping trackball: " + ev); if (qev != null) { @@ -4100,7 +4136,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (DEBUG_INPUT) Log.v(TAG, "Dispatch key: " + event); Object focusObj = mKeyWaiter.waitForNextEventTarget(event, null, - null, false, false); + null, false, false, pid, uid); if (focusObj == null) { Log.w(TAG, "No focus window, dropping: " + event); return INJECT_FAILED; @@ -4217,10 +4253,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState, deviceId, scancode, KeyEvent.FLAG_FROM_SYSTEM); - int result = dispatchKey(newEvent, Binder.getCallingPid(), Binder.getCallingUid()); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + final int result = dispatchKey(newEvent, pid, uid); if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true); + mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); } + Binder.restoreCallingIdentity(ident); switch (result) { case INJECT_NO_PERMISSION: throw new SecurityException( @@ -4241,10 +4281,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo * @return Returns true if event was dispatched, false if it was dropped for any reason */ public boolean injectPointerEvent(MotionEvent ev, boolean sync) { - int result = dispatchPointer(null, ev, Binder.getCallingPid(), Binder.getCallingUid()); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + final int result = dispatchPointer(null, ev, pid, uid); if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true); + mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); } + Binder.restoreCallingIdentity(ident); switch (result) { case INJECT_NO_PERMISSION: throw new SecurityException( @@ -4265,10 +4309,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo * @return Returns true if event was dispatched, false if it was dropped for any reason */ public boolean injectTrackballEvent(MotionEvent ev, boolean sync) { - int result = dispatchTrackball(null, ev, Binder.getCallingPid(), Binder.getCallingUid()); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + final int result = dispatchTrackball(null, ev, pid, uid); if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true); + mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); } + Binder.restoreCallingIdentity(ident); switch (result) { case INJECT_NO_PERMISSION: throw new SecurityException( @@ -4377,7 +4425,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo */ Object waitForNextEventTarget(KeyEvent nextKey, QueuedEvent qev, MotionEvent nextMotion, boolean isPointerEvent, - boolean failIfTimeout) { + boolean failIfTimeout, int callingPid, int callingUid) { long startTime = SystemClock.uptimeMillis(); long keyDispatchingTimeout = 5 * 1000; long waitedFor = 0; @@ -4395,7 +4443,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo ", mLastWin=" + mLastWin); if (targetIsNew) { Object target = findTargetWindow(nextKey, qev, nextMotion, - isPointerEvent); + isPointerEvent, callingPid, callingUid); if (target == SKIP_TARGET_TOKEN) { // The user has pressed a special key, and we are // dropping all pending events before it. @@ -4571,7 +4619,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } Object findTargetWindow(KeyEvent nextKey, QueuedEvent qev, - MotionEvent nextMotion, boolean isPointerEvent) { + MotionEvent nextMotion, boolean isPointerEvent, + int callingPid, int callingUid) { mOutsideTouchTargets = null; if (nextKey != null) { @@ -4580,9 +4629,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final int repeatCount = nextKey.getRepeatCount(); final boolean down = nextKey.getAction() != KeyEvent.ACTION_UP; boolean dispatch = mKeyWaiter.checkShouldDispatchKey(keycode); + if (!dispatch) { - mPolicy.interceptKeyTi(null, keycode, - nextKey.getMetaState(), down, repeatCount); + if (callingUid == 0 || + mContext.checkPermission( + android.Manifest.permission.INJECT_EVENTS, + callingPid, callingUid) + == PackageManager.PERMISSION_GRANTED) { + mPolicy.interceptKeyTi(null, keycode, + nextKey.getMetaState(), down, repeatCount); + } Log.w(TAG, "Event timeout during app switch: dropping " + nextKey); return SKIP_TARGET_TOKEN; @@ -4597,9 +4653,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); - if (mPolicy.interceptKeyTi(focus, - keycode, nextKey.getMetaState(), down, repeatCount)) { - return CONSUMED_EVENT_TOKEN; + if (callingUid == 0 || + (focus != null && callingUid == focus.mSession.mUid) || + mContext.checkPermission( + android.Manifest.permission.INJECT_EVENTS, + callingPid, callingUid) + == PackageManager.PERMISSION_GRANTED) { + if (mPolicy.interceptKeyTi(focus, + keycode, nextKey.getMetaState(), down, repeatCount)) { + return CONSUMED_EVENT_TOKEN; + } } return focus; diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 5236a91..b4c7afc 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -1524,8 +1524,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - final BatteryStatsImpl bstats = - (BatteryStatsImpl) mBatteryStatsService.getActiveStatistics(); + final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics(); synchronized(bstats) { synchronized(mPidsSelfLocked) { if (haveNewCpuStats) { @@ -1540,7 +1539,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); } else { BatteryStatsImpl.Uid.Proc ps = - bstats.getProcessStatsLocked(st.name); + bstats.getProcessStatsLocked(st.name, st.pid); if (ps != null) { ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); } @@ -10515,8 +10514,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // done with this agent public void unbindBackupAgent(ApplicationInfo appInfo) { if (DEBUG_BACKUP) Log.v(TAG, "unbindBackupAgent: " + appInfo); + if (appInfo == null) { + Log.w(TAG, "unbind backup agent for null app"); + return; + } synchronized(this) { + if (mBackupAppName == null) { + Log.w(TAG, "Unbinding backup agent with no active backup"); + return; + } + if (!mBackupAppName.equals(appInfo.packageName)) { Log.e(TAG, "Unbind of " + appInfo + " but is not the current backup target"); return; diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 77669c3..4ed0a5c6 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -701,17 +701,6 @@ public class PhoneNumberUtils } /** - * Note: calls extractNetworkPortion(), so do not use for - * SIM EF[ADN] style records - * - * Exceptions thrown if extractNetworkPortion(s).length() == 0 - */ - public static byte[] - networkPortionToCalledPartyBCD(String s) { - return numberToCalledPartyBCD(extractNetworkPortion(s)); - } - - /** * Return true iff the network portion of <code>address</code> is, * as far as we can tell on the device, suitable for use as an SMS * destination address. @@ -744,12 +733,25 @@ public class PhoneNumberUtils } /** + * Note: calls extractNetworkPortion(), so do not use for + * SIM EF[ADN] style records + * + * Returns null if network portion is empty. + */ + public static byte[] + networkPortionToCalledPartyBCD(String s) { + String networkPortion = extractNetworkPortion(s); + return numberToCalledPartyBCDHelper(networkPortion, false); + } + + /** * Same as {@link #networkPortionToCalledPartyBCD}, but includes a * one-byte length prefix. */ public static byte[] networkPortionToCalledPartyBCDWithLength(String s) { - return numberToCalledPartyBCDWithLength(extractNetworkPortion(s)); + String networkPortion = extractNetworkPortion(s); + return numberToCalledPartyBCDHelper(networkPortion, true); } /** @@ -761,61 +763,46 @@ public class PhoneNumberUtils */ public static byte[] numberToCalledPartyBCD(String number) { - // The extra byte required for '+' is taken into consideration while calculating - // length of ret. - int size = (hasPlus(number) ? number.length() - 1 : number.length()); - byte[] ret = new byte[(size + 1) / 2 + 1]; - - return numberToCalledPartyBCDHelper(ret, 0, number); + return numberToCalledPartyBCDHelper(number, false); } /** - * Same as {@link #numberToCalledPartyBCD}, but includes a - * one-byte length prefix. + * If includeLength is true, prepend a one-byte length value to + * the return array. */ private static byte[] - numberToCalledPartyBCDWithLength(String number) { - // The extra byte required for '+' is taken into consideration while calculating - // length of ret. - int size = (hasPlus(number) ? number.length() - 1 : number.length()); - int length = (size + 1) / 2 + 1; - byte[] ret = new byte[length + 1]; - - ret[0] = (byte) (length & 0xff); - return numberToCalledPartyBCDHelper(ret, 1, number); - } - - private static boolean - hasPlus(String s) { - return s.indexOf('+') >= 0; - } - - private static byte[] - numberToCalledPartyBCDHelper(byte[] ret, int offset, String number) { - if (hasPlus(number)) { - number = number.replaceAll("\\+", ""); - ret[offset] = (byte) TOA_International; - } else { - ret[offset] = (byte) TOA_Unknown; + numberToCalledPartyBCDHelper(String number, boolean includeLength) { + int numberLenReal = number.length(); + int numberLenEffective = numberLenReal; + boolean hasPlus = number.indexOf('+') != -1; + if (hasPlus) numberLenEffective--; + + if (numberLenEffective == 0) return null; + + int resultLen = (numberLenEffective + 1) / 2; // Encoded numbers require only 4 bits each. + int extraBytes = 1; // Prepended TOA byte. + if (includeLength) extraBytes++; // Optional prepended length byte. + resultLen += extraBytes; + + byte[] result = new byte[resultLen]; + + int digitCount = 0; + for (int i = 0; i < numberLenReal; i++) { + char c = number.charAt(i); + if (c == '+') continue; + int shift = ((digitCount & 0x01) == 1) ? 4 : 0; + result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift); + digitCount++; } - int size = number.length(); - int curChar = 0; - int countFullBytes = ret.length - offset - 1 - ((size - curChar) & 1); - for (int i = 1; i < 1 + countFullBytes; i++) { - ret[offset + i] - = (byte) ((charToBCD(number.charAt(curChar++))) - | (charToBCD(number.charAt(curChar++))) << 4); - } + // 1-fill any trailing odd nibble/quartet. + if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0; - // The left-over octet for odd-length phone numbers should be - // filled with 0xf. - if (countFullBytes + offset < ret.length - 1) { - ret[ret.length - 1] - = (byte) (charToBCD(number.charAt(curChar)) - | (0xf << 4)); - } - return ret; + int offset = 0; + if (includeLength) result[offset++] = (byte)(resultLen - 1); + result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown); + + return result; } /** all of 'a' up to len must match non-US trunk prefix ('0') */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index a79eb3a..bf5df88 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -612,6 +612,21 @@ public class TelephonyManager { } /** + * Returns the voice mail count. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * @hide + */ + public int getVoiceMessageCount() { + try { + return getITelephony().getVoiceMessageCount(); + } catch (RemoteException ex) { + } + return 0; + } + + /** * Retrieves the alphabetic identifier associated with the voice * mail number. * <p> diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java index af79404..c074cb8 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java @@ -98,8 +98,8 @@ public abstract class DataConnectionTracker extends Handler { //***** Constants protected static final int RECONNECT_DELAY_INITIAL_MILLIS = 5 * 1000; - /** Cap out with 1 hour retry interval. */ - protected static final int RECONNECT_DELAY_MAX_MILLIS = 60 * 60 * 1000; + /** Cap out with 30 min retry interval. */ + protected static final int RECONNECT_DELAY_MAX_MILLIS = 30 * 60 * 1000; /** Slow poll when attempting connection recovery. */ protected static final int POLL_NETSTAT_SLOW_MILLIS = 5000; diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 6e6f64c..d83b135 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -241,7 +241,7 @@ interface ITelephony { /** * Returns the unread count of voicemails */ - int getCountVoiceMessages(); + int getVoiceMessageCount(); } diff --git a/telephony/java/com/android/internal/telephony/IccRecords.java b/telephony/java/com/android/internal/telephony/IccRecords.java index 114094b..ea24c25 100644 --- a/telephony/java/com/android/internal/telephony/IccRecords.java +++ b/telephony/java/com/android/internal/telephony/IccRecords.java @@ -196,7 +196,7 @@ public abstract class IccRecords extends Handler implements IccConstants { * If not available (eg, on an older CPHS SIM) -1 is returned if * getVoiceMessageWaiting() is true */ - public int getCountVoiceMessages() { + public int getVoiceMessageCount() { return countVoiceMessages; } diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java index cdbfd61..c8d384d 100644 --- a/telephony/java/com/android/internal/telephony/Phone.java +++ b/telephony/java/com/android/internal/telephony/Phone.java @@ -831,7 +831,7 @@ public interface Phone { * Returns unread voicemail count. This count is shown when the voicemail * notification is expanded.<p> */ - int getCountVoiceMessages(); + int getVoiceMessageCount(); /** * Returns the alpha tag associated with the voice mail number. diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java index c34f26e..f6665aa 100644 --- a/telephony/java/com/android/internal/telephony/PhoneBase.java +++ b/telephony/java/com/android/internal/telephony/PhoneBase.java @@ -651,6 +651,11 @@ public abstract class PhoneBase implements Phone { mNotifier.notifyDataActivity(this); } + public void notifyMessageWaitingIndicator() { + // This function is added to send the notification to DefaultPhoneNotifier. + mNotifier.notifyMessageWaitingChanged(this); + } + public void notifyDataConnection(String reason) { mNotifier.notifyDataConnection(this, reason); } @@ -658,7 +663,7 @@ public abstract class PhoneBase implements Phone { public abstract String getPhoneName(); /** @hide */ - public int getCountVoiceMessages(){ + public int getVoiceMessageCount(){ return 0; } diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java index 4bb24dc..5b3c8dd 100644 --- a/telephony/java/com/android/internal/telephony/PhoneProxy.java +++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java @@ -435,8 +435,8 @@ public class PhoneProxy extends Handler implements Phone { } /** @hide */ - public int getCountVoiceMessages(){ - return mActivePhone.getCountVoiceMessages(); + public int getVoiceMessageCount(){ + return mActivePhone.getVoiceMessageCount(); } public String getVoiceMailAlphaTag() { diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java index 9f780c9..4db3e5b 100644 --- a/telephony/java/com/android/internal/telephony/RIL.java +++ b/telephony/java/com/android/internal/telephony/RIL.java @@ -1365,7 +1365,6 @@ public final class RIL extends BaseCommands implements CommandsInterface { RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, result); - rr.mp.writeInt(2); rr.mp.writeInt(success ? 0 : 1); //RIL_CDMA_SMS_ErrorClass // cause code according to X.S004-550E rr.mp.writeInt(cause); diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java index a3016fa..890ea63 100644 --- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java @@ -78,6 +78,7 @@ public abstract class SMSDispatcher extends Handler { protected static final String[] RAW_PROJECTION = new String[] { "pdu", "sequence", + "destination_port", }; static final int MAIL_SEND_SMS = 1; @@ -289,7 +290,13 @@ public abstract class SMSDispatcher extends Handler { sms = (SmsMessage) ar.result; try { if (mStorageAvailable) { - dispatchMessage(sms.mWrappedSmsMessage); + int result = dispatchMessage(sms.mWrappedSmsMessage); + if (result != Activity.RESULT_OK) { + // RESULT_OK means that message was broadcast for app(s) to handle. + // Any other result, we should ack here. + boolean handled = (result == Intents.RESULT_SMS_HANDLED); + acknowledgeLastIncomingSms(handled, result, null); + } } else { acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_OUT_OF_MEMORY, null); } @@ -469,8 +476,11 @@ public abstract class SMSDispatcher extends Handler { * Dispatches an incoming SMS messages. * * @param sms the incoming message from the phone + * @return a result code from {@link Telephony.Sms.Intents}, or + * {@link Activity#RESULT_OK} if the message has been broadcast + * to applications */ - protected abstract void dispatchMessage(SmsMessageBase sms); + protected abstract int dispatchMessage(SmsMessageBase sms); /** @@ -478,8 +488,11 @@ public abstract class SMSDispatcher extends Handler { * the part is stored for later processing. * * NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null. + * @return a result code from {@link Telephony.Sms.Intents}, or + * {@link Activity#RESULT_OK} if the message has been broadcast + * to applications */ - protected void processMessagePart(SmsMessageBase sms, + protected int processMessagePart(SmsMessageBase sms, SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) { // Lookup all other related parts @@ -506,8 +519,7 @@ public abstract class SMSDispatcher extends Handler { values.put("destination_port", portAddrs.destPort); } mResolver.insert(mRawUri, values); - acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null); - return; + return Intents.RESULT_SMS_HANDLED; } // All the parts are in place, deal with them @@ -529,8 +541,7 @@ public abstract class SMSDispatcher extends Handler { } catch (SQLException e) { Log.e(TAG, "Can't access multipart SMS database", e); // TODO: Would OUT_OF_MEMORY be more appropriate? - acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null); - return; + return Intents.RESULT_SMS_GENERIC_ERROR; } finally { if (cursor != null) cursor.close(); } @@ -555,7 +566,7 @@ public abstract class SMSDispatcher extends Handler { output.write(data, 0, data.length); } // Handle the PUSH - mWapPush.dispatchWapPdu(output.toByteArray()); + return mWapPush.dispatchWapPdu(output.toByteArray()); } else { // The messages were sent to a port, so concoct a URI for it dispatchPortAddressedPdus(pdus, portAddrs.destPort); @@ -564,6 +575,7 @@ public abstract class SMSDispatcher extends Handler { // The messages were not sent to a port dispatchPdus(pdus); } + return Activity.RESULT_OK; } /** diff --git a/telephony/java/com/android/internal/telephony/WapPushOverSms.java b/telephony/java/com/android/internal/telephony/WapPushOverSms.java index a9aacda..9970940 100644 --- a/telephony/java/com/android/internal/telephony/WapPushOverSms.java +++ b/telephony/java/com/android/internal/telephony/WapPushOverSms.java @@ -16,8 +16,10 @@ package com.android.internal.telephony; +import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.util.Config; import android.util.Log; @@ -51,8 +53,11 @@ public class WapPushOverSms { * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format. * * @param pdu The WAP PDU, made up of one or more SMS PDUs + * @return a result code from {@link Telephony.Sms.Intents}, or + * {@link Activity#RESULT_OK} if the message has been broadcast + * to applications */ - public void dispatchWapPdu(byte[] pdu) { + public int dispatchWapPdu(byte[] pdu) { if (Config.LOGD) Log.d(LOG_TAG, "Rx: " + IccUtils.bytesToHexString(pdu)); @@ -64,7 +69,7 @@ public class WapPushOverSms { if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) && (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) { if (Config.LOGD) Log.w(LOG_TAG, "Received non-PUSH WAP PDU. Type = " + pduType); - return; + return Intents.RESULT_SMS_HANDLED; } pduDecoder = new WspTypeDecoder(pdu); @@ -77,7 +82,7 @@ public class WapPushOverSms { */ if (pduDecoder.decodeUintvarInteger(index) == false) { if (Config.LOGD) Log.w(LOG_TAG, "Received PDU. Header Length error."); - return; + return Intents.RESULT_SMS_GENERIC_ERROR; } headerLength = (int)pduDecoder.getValue32(); index += pduDecoder.getDecodedDataLength(); @@ -98,7 +103,7 @@ public class WapPushOverSms { */ if (pduDecoder.decodeContentType(index) == false) { if (Config.LOGD) Log.w(LOG_TAG, "Received PDU. Header Content-Type error."); - return; + return Intents.RESULT_SMS_GENERIC_ERROR; } int binaryContentType; String mimeType = pduDecoder.getValueString(); @@ -128,7 +133,7 @@ public class WapPushOverSms { Log.w(LOG_TAG, "Received PDU. Unsupported Content-Type = " + binaryContentType); } - return; + return Intents.RESULT_SMS_HANDLED; } } else { if (mimeType.equals(WspTypeDecoder.CONTENT_MIME_TYPE_B_DRM_RIGHTS_XML)) { @@ -145,7 +150,7 @@ public class WapPushOverSms { binaryContentType = WspTypeDecoder.CONTENT_TYPE_B_MMS; } else { if (Config.LOGD) Log.w(LOG_TAG, "Received PDU. Unknown Content-Type = " + mimeType); - return; + return Intents.RESULT_SMS_HANDLED; } } index += pduDecoder.getDecodedDataLength(); @@ -167,6 +172,7 @@ public class WapPushOverSms { if (dispatchedByApplication == false) { dispatchWapPdu_default(pdu, transactionId, pduType, mimeType, dataIndex); } + return Activity.RESULT_OK; } private void dispatchWapPdu_default( diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java index 9aa7eab..3362de8 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -19,6 +19,7 @@ package com.android.internal.telephony.cdma; import android.app.ActivityManagerNative; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.AsyncResult; import android.os.Handler; import android.os.Looper; @@ -26,6 +27,7 @@ import android.os.Message; import android.os.Registrant; import android.os.RegistrantList; import android.os.SystemProperties; +import android.preference.PreferenceManager; import android.provider.Settings; import android.telephony.CellLocation; import android.telephony.PhoneNumberUtils; @@ -40,6 +42,7 @@ import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.Connection; import com.android.internal.telephony.DataConnection; import com.android.internal.telephony.IccCard; +import com.android.internal.telephony.IccException; import com.android.internal.telephony.IccFileHandler; import com.android.internal.telephony.IccPhoneBookInterfaceManager; import com.android.internal.telephony.IccSmsInterfaceManager; @@ -65,6 +68,9 @@ public class CDMAPhone extends PhoneBase { // Default Emergency Callback Mode exit timer private static final int DEFAULT_ECM_EXIT_TIMER_VALUE = 30000; + static final String VM_COUNT_CDMA = "vm_count_key_cdma"; + private static final String VM_NUMBER_CDMA = "vm_number_key_cdma"; + private String mVmNumber = null; //***** Instance Variables CdmaCallTracker mCT; @@ -147,6 +153,9 @@ public class CDMAPhone extends PhoneBase { // This is needed to handle phone process crashes String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); mIsPhoneInECMState = inEcm.equals("true"); + + // Notify voicemails. + notifier.notifyMessageWaitingChanged(this); } public void dispose() { @@ -300,7 +309,7 @@ public class CDMAPhone extends PhoneBase { public boolean getMessageWaitingIndicator() { - return mRuimRecords.getVoiceMessageWaiting(); + return (getVoiceMessageCount() > 0); } public List<? extends MmiCode> @@ -678,22 +687,33 @@ public class CDMAPhone extends PhoneBase { public void setVoiceMailNumber(String alphaTag, String voiceMailNumber, Message onComplete) { - //mSIMRecords.setVoiceMailNumber(alphaTag, voiceMailNumber, onComplete); - //TODO: Where do we have to store this value has to be clarified with QC + Message resp; + mVmNumber = voiceMailNumber; + resp = h.obtainMessage(EVENT_SET_VM_NUMBER_DONE, 0, 0, onComplete); + mRuimRecords.setVoiceMailNumber(alphaTag, mVmNumber, resp); } public String getVoiceMailNumber() { - //TODO: Where can we get this value has to be clarified with QC - //return mSIMRecords.getVoiceMailNumber(); -// throw new RuntimeException(); - return "*86"; + String number = null; + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + // TODO(Moto): The default value of voicemail number should be read from a system property + number = sp.getString(VM_NUMBER_CDMA, "*86"); + return number; } /* Returns Number of Voicemails * @hide */ - public int getCountVoiceMessages() { - return mRuimRecords.getCountVoiceMessages(); + public int getVoiceMessageCount() { + int voicemailCount = mRuimRecords.getVoiceMessageCount(); + // If mRuimRecords.getVoiceMessageCount returns zero, then there is possibility + // that phone was power cycled and would have lost the voicemail count. + // So get the count from preferences. + if (voicemailCount == 0) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + voicemailCount = sp.getInt(VM_COUNT_CDMA, 0); + } + return voicemailCount; } public String getVoiceMailAlphaTag() { @@ -820,9 +840,10 @@ public class CDMAPhone extends PhoneBase { mRuimRecords.setVoiceMessageWaiting(1, mwi ? -1 : 0); } - public void - notifyMessageWaitingIndicator() { - mNotifier.notifyMessageWaitingChanged(this); + /* This function is overloaded to send number of voicemails instead of sending true/false */ + /*package*/ void + updateMessageWaitingIndicator(int mwi) { + mRuimRecords.setVoiceMessageWaiting(1, mwi); } /** @@ -984,6 +1005,19 @@ public class CDMAPhone extends PhoneBase { } break; + case EVENT_SET_VM_NUMBER_DONE:{ + ar = (AsyncResult)msg.obj; + if (IccException.class.isInstance(ar.exception)) { + storeVoiceMailNumber(mVmNumber); + ar.exception = null; + } + onComplete = (Message) ar.userObj; + if (onComplete != null) { + AsyncResult.forMessage(onComplete, ar.result, ar.exception); + onComplete.sendToTarget(); + } + } + default:{ throw new RuntimeException("unexpected event not handled"); } @@ -1198,4 +1232,16 @@ public class CDMAPhone extends PhoneBase { int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator(); return mEriManager.getCdmaEriText(roamInd, defRoamInd); } + + /** + * Store the voicemail number in preferences + */ + private void storeVoiceMailNumber(String number) { + // Update the preference value of voicemail number + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + SharedPreferences.Editor editor = sp.edit(); + editor.putString(VM_NUMBER_CDMA, number); + editor.commit(); + } + } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java index da9fd0c..ed2ea90 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java @@ -253,11 +253,7 @@ public final class CdmaCallTracker extends CallTracker { // Always unmute when answering a new call setMute(false); cm.acceptCall(obtainCompleteMessage()); - } else if ((foregroundCall.connections.size() > 0) && - (ringingCall.getState() == CdmaCall.State.WAITING)) { - // TODO(Moto): jsh asks, "Is this check necessary? I don't think it should be - // possible to have no fg connection and a WAITING call, but if we should hit - // this situation, is a CallStateExcetion appropriate?" + } else if (ringingCall.getState() == CdmaCall.State.WAITING) { CdmaConnection cwConn = (CdmaConnection)(ringingCall.getLatestConnection()); // Since there is no network response for supplimentary @@ -530,10 +526,6 @@ public final class CdmaCallTracker extends CallTracker { CdmaConnection cn = (CdmaConnection)foregroundCall.connections.get(n); droppedDuringPoll.add(cn); } - // TODO(Moto): jsh asks, "Are we sure we don't need to do this for - // ringingCall as well? What if there's a WAITING call (ie, UI timer - // hasn't expired, moving it to DISCONNECTED)? How/when will it - // transition to DISCONNECTED?" } foregroundCall.setGeneric(false); // Dropped connections are removed from the CallTracker @@ -681,8 +673,12 @@ public final class CdmaCallTracker extends CallTracker { // set the ringing call state to IDLE here to avoid a race condition // where a new call waiting could get a hang up from an old call // waiting ringingCall. - // TODO(Moto): jsh asks, "Should we call conn.ondisconnect() here or Somewhere?" - ringingCall.detach(conn); + // + // PhoneApp does the call log itself since only PhoneApp knows + // the hangup reason is user ignoring or timing out. So conn.onDisconnect() + // is not called here. Instead, conn.onLocalDisconnect() is called. + conn.onLocalDisconnect(); + phone.notifyCallStateChanged(); return; } else { try { diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java b/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java index 588bdf0..025382d 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java @@ -452,12 +452,7 @@ public class CdmaConnection extends Connection { this.cause = cause; if (!disconnected) { - index = -1; - - disconnectTime = System.currentTimeMillis(); - duration = SystemClock.elapsedRealtime() - connectTimeReal; - disconnected = true; - + doDisconnect(); if (Config.LOGD) Log.d(LOG_TAG, "[CDMAConn] onDisconnect: cause=" + cause); @@ -470,6 +465,21 @@ public class CdmaConnection extends Connection { releaseWakeLock(); } + /** Called when the call waiting connection has been hung up */ + /*package*/ void + onLocalDisconnect() { + if (!disconnected) { + doDisconnect(); + if (Config.LOGD) Log.d(LOG_TAG, + "[CDMAConn] onLoalDisconnect" ); + + if (parent != null) { + parent.detach(this); + } + } + releaseWakeLock(); + } + // Returns true if state has changed, false if nothing changed /*package*/ boolean update (DriverCall dc) { @@ -587,6 +597,14 @@ public class CdmaConnection extends Connection { } private void + doDisconnect() { + index = -1; + disconnectTime = System.currentTimeMillis(); + duration = SystemClock.elapsedRealtime() - connectTimeReal; + disconnected = true; + } + + private void onStartedHolding() { holdingStartTime = SystemClock.elapsedRealtime(); } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java index 2a65de3..9d29272 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java @@ -26,22 +26,18 @@ import android.content.SharedPreferences; import android.net.NetworkInfo; import android.net.wifi.WifiManager; import android.os.AsyncResult; -import android.os.Handler; import android.os.INetStatService; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.SystemProperties; import android.preference.PreferenceManager; import android.provider.Checkin; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.telephony.cdma.CdmaCellLocation; -import android.util.EventLog; import android.text.TextUtils; +import android.util.EventLog; import android.util.Log; import com.android.internal.telephony.CommandsInterface; @@ -50,7 +46,6 @@ import com.android.internal.telephony.DataConnection; import com.android.internal.telephony.DataConnection.FailCause; import com.android.internal.telephony.DataConnectionTracker; import com.android.internal.telephony.Phone; -import com.android.internal.telephony.PhoneProxy; import com.android.internal.telephony.TelephonyEventLog; import java.util.ArrayList; @@ -67,6 +62,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { // Indicates baseband will not auto-attach private boolean noAutoAttach = false; long nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + private boolean mReregisterOnReconnectFailure = false; private boolean mIsScreenOn = true; //useful for debugging @@ -464,6 +460,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { startNetStatPoll(); // reset reconnect timer nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + mReregisterOnReconnectFailure = false; } private void resetPollStats() { @@ -619,6 +616,21 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { private void reconnectAfterFail(FailCause lastFailCauseCode, String reason) { if (state == State.FAILED) { + if (nextReconnectDelay > RECONNECT_DELAY_MAX_MILLIS) { + if (mReregisterOnReconnectFailure) { + // We have already tried to re-register to the network. + // This might be a problem with the data network. + nextReconnectDelay = RECONNECT_DELAY_MAX_MILLIS; + } else { + // Try to Re-register to the network. + Log.d(LOG_TAG, "PDP activate failed, Reregistering to the network"); + mReregisterOnReconnectFailure = true; + mCdmaPhone.mSST.reRegisterNetwork(null); + nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + return; + } + } + Log.d(LOG_TAG, "Data Connection activate failed. Scheduling next attempt for " + (nextReconnectDelay / 1000) + "s"); @@ -634,9 +646,6 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { // double it for next time nextReconnectDelay *= 2; - if (nextReconnectDelay > RECONNECT_DELAY_MAX_MILLIS) { - nextReconnectDelay = RECONNECT_DELAY_MAX_MILLIS; - } if (!shouldPostNotification(lastFailCauseCode)) { Log.d(LOG_TAG,"NOT Posting Data Connection Unavailable notification " @@ -716,6 +725,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { // Make sure our reconnect delay starts at the initial value // next time the radio comes on nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + mReregisterOnReconnectFailure = false; if (phone.getSimulatedRadioControl() != null) { // Assume data is connected on the simulator @@ -793,6 +803,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { } else { // reset reconnect timer nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + mReregisterOnReconnectFailure = false; // in case data setup was attempted when we were on a voice call trySetupData(Phone.REASON_VOICE_CALL_ENDED); } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index 2b4a700..ecdc8f6 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -20,11 +20,14 @@ package com.android.internal.telephony.cdma; import android.app.Activity; import android.app.PendingIntent; import android.content.ContentValues; +import android.content.SharedPreferences; import android.database.Cursor; import android.database.SQLException; import android.os.AsyncResult; import android.os.Message; +import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; +import android.preference.PreferenceManager; import android.util.Config; import android.util.Log; @@ -64,17 +67,12 @@ final class CdmaSMSDispatcher extends SMSDispatcher { Log.d(TAG, "handleStatusReport is a special GSM function, should never be called in CDMA!"); } - /** - * Dispatches an incoming SMS messages. - * - * @param smsb the incoming message from the phone - */ - protected void dispatchMessage(SmsMessageBase smsb) { + /** {@inheritDoc} */ + protected int dispatchMessage(SmsMessageBase smsb) { // If sms is null, means there was a parsing error. - // TODO: Should NAK this. if (smsb == null) { - return; + return Intents.RESULT_SMS_GENERIC_ERROR; } // Decode BD stream and set sms variables. @@ -83,27 +81,6 @@ final class CdmaSMSDispatcher extends SMSDispatcher { int teleService = sms.getTeleService(); boolean handled = false; - // Teleservices W(E)MT and VMN are handled together: - if ((teleService == SmsEnvelope.TELESERVICE_WMT) - || (teleService == SmsEnvelope.TELESERVICE_WEMT) - || (teleService == SmsEnvelope.TELESERVICE_VMN)) { - // From here on we need decoded BD. - // Special case the message waiting indicator messages - if (sms.isMWISetMessage()) { - mCdmaPhone.updateMessageWaitingIndicator(true); - handled |= sms.isMwiDontStore(); - if (Config.LOGD) { - Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled); - } - } else if (sms.isMWIClearMessage()) { - mCdmaPhone.updateMessageWaitingIndicator(false); - handled |= sms.isMwiDontStore(); - if (Config.LOGD) { - Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled); - } - } - } - if (sms.getUserData() == null) { if (Config.LOGD) { Log.d(TAG, "Received SMS without user data"); @@ -111,11 +88,25 @@ final class CdmaSMSDispatcher extends SMSDispatcher { handled = true; } - if (handled) return; + if (handled) { + return Intents.RESULT_SMS_HANDLED; + } if (SmsEnvelope.TELESERVICE_WAP == teleService){ - processCdmaWapPdu(sms.getUserData(), sms.messageRef, sms.getOriginatingAddress()); - return; + return processCdmaWapPdu(sms.getUserData(), sms.messageRef, + sms.getOriginatingAddress()); + } else if (SmsEnvelope.TELESERVICE_VMN == teleService) { + // handling Voicemail + int voicemailCount = sms.getNumOfVoicemails(); + Log.d(TAG, "Voicemail count=" + voicemailCount); + // Store the voicemail count in preferences. + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( + ((CDMAPhone) mPhone).getContext()); + SharedPreferences.Editor editor = sp.edit(); + editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount); + editor.commit(); + ((CDMAPhone) mPhone).updateMessageWaitingIndicator(voicemailCount); + return Intents.RESULT_SMS_HANDLED; } /** @@ -145,17 +136,19 @@ final class CdmaSMSDispatcher extends SMSDispatcher { if (smsHeader != null && smsHeader.portAddrs != null) { if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) { // GSM-style WAP indication - mWapPush.dispatchWapPdu(sms.getUserData()); + return mWapPush.dispatchWapPdu(sms.getUserData()); + } else { + // The message was sent to a port, so concoct a URI for it. + dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort); } - // The message was sent to a port, so concoct a URI for it. - dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort); } else { // Normal short and non-port-addressed message, dispatch it. dispatchPdus(pdus); } + return Activity.RESULT_OK; } else { // Process the message part. - processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs); + return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs); } } @@ -165,29 +158,35 @@ final class CdmaSMSDispatcher extends SMSDispatcher { * WDP segments are gathered until a datagram completes and gets dispatched. * * @param pdu The WAP-WDP PDU segment + * @return a result code from {@link Telephony.Sms.Intents}, or + * {@link Activity#RESULT_OK} if the message has been broadcast + * to applications */ - protected void processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) { + protected int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address) { int segment; int totalSegments; int index = 0; int msgType; - int sourcePort; - int destinationPort; + int sourcePort = 0; + int destinationPort = 0; msgType = pdu[index++]; if (msgType != 0){ Log.w(TAG, "Received a WAP SMS which is not WDP. Discard."); - return; + return Intents.RESULT_SMS_HANDLED; } totalSegments = pdu[index++]; // >=1 segment = pdu[index++]; // >=0 - //process WDP segment - sourcePort = (0xFF & pdu[index++]) << 8; - sourcePort |= 0xFF & pdu[index++]; - destinationPort = (0xFF & pdu[index++]) << 8; - destinationPort |= 0xFF & pdu[index++]; + // Only the first segment contains sourcePort and destination Port + if (segment == 0) { + //process WDP segment + sourcePort = (0xFF & pdu[index++]) << 8; + sourcePort |= 0xFF & pdu[index++]; + destinationPort = (0xFF & pdu[index++]) << 8; + destinationPort |= 0xFF & pdu[index++]; + } // Lookup all other related parts StringBuilder where = new StringBuilder("reference_number ="); @@ -217,7 +216,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { mResolver.insert(mRawUri, values); - return; + return Intents.RESULT_SMS_HANDLED; } // All the parts are in place, deal with them @@ -228,6 +227,11 @@ final class CdmaSMSDispatcher extends SMSDispatcher { for (int i = 0; i < cursorCount; i++) { cursor.moveToNext(); int cursorSequence = (int)cursor.getLong(sequenceColumn); + // Read the destination port from the first segment + if (cursorSequence == 0) { + int destinationPortColumn = cursor.getColumnIndex("destination_port"); + destinationPort = (int)cursor.getLong(destinationPortColumn); + } pdus[cursorSequence] = HexDump.hexStringToByteArray( cursor.getString(pduColumn)); } @@ -237,7 +241,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { mResolver.delete(mRawUri, where.toString(), whereArgs); } catch (SQLException e) { Log.e(TAG, "Can't access multipart SMS database", e); - return; // TODO: NACK the message or something, don't just discard. + return Intents.RESULT_SMS_GENERIC_ERROR; } finally { if (cursor != null) cursor.close(); } @@ -257,15 +261,14 @@ final class CdmaSMSDispatcher extends SMSDispatcher { switch (destinationPort) { case SmsHeader.PORT_WAP_PUSH: // Handle the PUSH - mWapPush.dispatchWapPdu(datagram); - break; + return mWapPush.dispatchWapPdu(datagram); default:{ pdus = new byte[1][]; pdus[0] = datagram; // The messages were sent to any other WAP port dispatchPortAddressedPdus(pdus, destinationPort); - break; + return Activity.RESULT_OK; } } } diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 63d2c47..3a92064 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -773,6 +773,12 @@ public class SmsMessage extends SmsMessageBase { return asciiDigit; } + /** This function shall be called to get the number of voicemails. + * @hide + */ + /*package*/ int getNumOfVoicemails() { + return mBearerData.numberOfMessages; + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index a835dee..eaeda64 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -83,7 +83,7 @@ public final class BearerData { public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07; public static final int MESSAGE_TYPE_SUBMIT_REPORT = 0x08; - public byte messageType; + public int messageType; /** * 16-bit value indicating the message ID, which increments modulo 65536. @@ -102,7 +102,7 @@ public final class BearerData { public static final int PRIORITY_EMERGENCY = 0x3; public boolean priorityIndicatorSet = false; - public byte priority = PRIORITY_NORMAL; + public int priority = PRIORITY_NORMAL; /** * Supported privacy modes for CDMA SMS messages @@ -114,7 +114,7 @@ public final class BearerData { public static final int PRIVACY_SECRET = 0x3; public boolean privacyIndicatorSet = false; - public byte privacy = PRIVACY_NOT_RESTRICTED; + public int privacy = PRIVACY_NOT_RESTRICTED; /** * Supported alert priority modes for CDMA SMS messages @@ -139,7 +139,7 @@ public final class BearerData { public static final int DISPLAY_MODE_USER = 0x2; public boolean displayModeSet = false; - public byte displayMode = DISPLAY_MODE_DEFAULT; + public int displayMode = DISPLAY_MODE_DEFAULT; /** * Language Indicator values. NOTE: the spec (3GPP2 C.S0015-B, @@ -799,7 +799,7 @@ public final class BearerData { private static void decodeUserData(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException { - byte paramBytes = inStream.read(8); + int paramBytes = inStream.read(8); bData.userData = new UserData(); bData.userData.msgEncoding = inStream.read(5); bData.userData.msgEncodingSet = true; @@ -867,7 +867,7 @@ public final class BearerData { inStream.skip(offset); byte[] expandedData = new byte[numFields]; for (int i = 0; i < numFields; i++) { - expandedData[i] = inStream.read(7); + expandedData[i] = (byte)inStream.read(7); } return new String(expandedData, 0, numFields, "US-ASCII"); } catch (java.io.UnsupportedEncodingException ex) { @@ -919,10 +919,126 @@ public final class BearerData { } } + /** + * IS-91 Voice Mail message decoding + * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) + * (For character encodings, see TIA/EIA/IS-91, Annex B) + * + * Protocol Summary: The user data payload may contain 3-14 + * characters. The first two characters are parsed as a number + * and indicate the number of voicemails. The third character is + * either a SPACE or '!' to indicate normal or urgent priority, + * respectively. Any following characters are treated as normal + * text user data payload. + * + * Note that the characters encoding is 6-bit packed. + */ + private static void decodeIs91VoicemailStatus(BearerData bData) + throws BitwiseInputStream.AccessException, CodingException + { + BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); + int dataLen = inStream.available() / 6; // 6-bit packed character encoding. + int numFields = bData.userData.numFields; + if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) { + throw new CodingException("IS-91 voicemail status decoding failed"); + } + try { + StringBuffer strbuf = new StringBuffer(dataLen); + while (inStream.available() >= 6) { + strbuf.append(UserData.IA5_MAP[inStream.read(6)]); + } + String data = strbuf.toString(); + bData.numberOfMessages = Integer.parseInt(data.substring(0, 2)); + char prioCode = data.charAt(2); + if (prioCode == ' ') { + bData.priority = PRIORITY_NORMAL; + } else if (prioCode == '!') { + bData.priority = PRIORITY_URGENT; + } else { + throw new CodingException("IS-91 voicemail status decoding failed: " + + "illegal priority setting (" + prioCode + ")"); + } + bData.priorityIndicatorSet = true; + bData.userData.payloadStr = data.substring(3, numFields - 3); + } catch (java.lang.NumberFormatException ex) { + throw new CodingException("IS-91 voicemail status decoding failed: " + ex); + } catch (java.lang.IndexOutOfBoundsException ex) { + throw new CodingException("IS-91 voicemail status decoding failed: " + ex); + } + } + + /** + * IS-91 Short Message decoding + * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) + * (For character encodings, see TIA/EIA/IS-91, Annex B) + * + * Protocol Summary: The user data payload may contain 1-14 + * characters, which are treated as normal text user data payload. + * Note that the characters encoding is 6-bit packed. + */ + private static void decodeIs91ShortMessage(BearerData bData) + throws BitwiseInputStream.AccessException, CodingException + { + BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); + int dataLen = inStream.available() / 6; // 6-bit packed character encoding. + int numFields = bData.userData.numFields; + if ((dataLen > 14) || (dataLen < numFields)) { + throw new CodingException("IS-91 voicemail status decoding failed"); + } + StringBuffer strbuf = new StringBuffer(dataLen); + for (int i = 0; i < numFields; i++) { + strbuf.append(UserData.IA5_MAP[inStream.read(6)]); + } + bData.userData.payloadStr = strbuf.toString(); + } + + /** + * IS-91 CLI message (callback number) decoding + * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) + * + * Protocol Summary: The data payload may contain 1-32 digits, + * encoded using standard 4-bit DTMF, which are treated as a + * callback number. + */ + private static void decodeIs91Cli(BearerData bData) throws CodingException { + BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); + int dataLen = inStream.available() / 4; // 4-bit packed DTMF digit encoding. + int numFields = bData.userData.numFields; + if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) { + throw new CodingException("IS-91 voicemail status decoding failed"); + } + CdmaSmsAddress addr = new CdmaSmsAddress(); + addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF; + addr.origBytes = bData.userData.payload; + addr.numberOfDigits = (byte)numFields; + decodeSmsAddress(addr); + bData.callbackNumber = addr; + } + + private static void decodeIs91(BearerData bData) + throws BitwiseInputStream.AccessException, CodingException + { + switch (bData.userData.msgType) { + case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS: + decodeIs91VoicemailStatus(bData); + break; + case UserData.IS91_MSG_TYPE_CLI: + decodeIs91Cli(bData); + break; + case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL: + case UserData.IS91_MSG_TYPE_SHORT_MESSAGE: + decodeIs91ShortMessage(bData); + break; + default: + throw new CodingException("unsupported IS-91 message type (" + + bData.userData.msgType + ")"); + } + } + private static void decodeReplyOption(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException { - byte paramBytes = inStream.read(8); + int paramBytes = inStream.read(8); if (paramBytes != 1) { throw new CodingException("REPLY_OPTION subparam size incorrect"); } @@ -985,7 +1101,7 @@ public final class BearerData { private static void decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException { - byte paramBytes = inStream.read(8); + int paramBytes = inStream.read(8); CdmaSmsAddress addr = new CdmaSmsAddress(); addr.digitMode = inStream.read(1); byte fieldBits = 4; @@ -1219,7 +1335,18 @@ public final class BearerData { throw new CodingException("missing MESSAGE_IDENTIFIER subparam"); } if (bData.userData != null) { - decodeUserDataPayload(bData.userData, bData.hasUserDataHeader); + if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { + if ((foundSubparamMask ^ + (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^ + (1 << SUBPARAM_USER_DATA)) + != 0) { + Log.e(LOG_TAG, "IS-91 must occur without extra subparams (" + + foundSubparamMask + ")"); + } + decodeIs91(bData); + } else { + decodeUserDataPayload(bData.userData, bData.hasUserDataHeader); + } } return bData; } catch (BitwiseInputStream.AccessException ex) { diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java index 917ec9d..4d79966 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java @@ -29,7 +29,7 @@ public class CdmaSmsAddress extends SmsAddress { static public final int DIGIT_MODE_4BIT_DTMF = 0x00; static public final int DIGIT_MODE_8BIT_CHAR = 0x01; - public byte digitMode; + public int digitMode; /** * Number Mode Indicator is 1-bit value that indicates whether the @@ -39,7 +39,7 @@ public class CdmaSmsAddress extends SmsAddress { static public final int NUMBER_MODE_NOT_DATA_NETWORK = 0x00; static public final int NUMBER_MODE_DATA_NETWORK = 0x01; - public byte numberMode; + public int numberMode; /** * Number Types for data networks. @@ -65,7 +65,7 @@ public class CdmaSmsAddress extends SmsAddress { * This field shall be set to the number of address digits * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) */ - public byte numberOfDigits; + public int numberOfDigits; /** * Numbering Plan identification is a 0 or 4-bit value that @@ -78,7 +78,7 @@ public class CdmaSmsAddress extends SmsAddress { //static protected final int NUMBERING_PLAN_TELEX = 0x4; //static protected final int NUMBERING_PLAN_PRIVATE = 0x9; - public byte numberPlan; + public int numberPlan; /** * NOTE: the parsed string address and the raw byte array values diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java index d8a48cc..34cbbfa 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java @@ -40,12 +40,26 @@ public class UserData { public static final int ENCODING_GSM_DCS = 0x0A; /** + * IS-91 message types. + * (See TIA/EIS/IS-91-A-ENGL 1999, table 3.7.1.1-3) + */ + public static final int IS91_MSG_TYPE_VOICEMAIL_STATUS = 0x82; + public static final int IS91_MSG_TYPE_SHORT_MESSAGE_FULL = 0x83; + public static final int IS91_MSG_TYPE_CLI = 0x84; + public static final int IS91_MSG_TYPE_SHORT_MESSAGE = 0x85; + + /** * IA5 data encoding character mappings. * (See CCITT Rec. T.50 Tables 1 and 3) * * Note this mapping is the the same as for printable ASCII * characters, with a 0x20 offset, meaning that the ASCII SPACE * character occurs with code 0x20. + * + * Note this mapping is also equivalent to that used by the IS-91 + * protocol, except for the latter using only 6 bits, and hence + * mapping only entries up to the '_' character. + * */ public static final char[] IA5_MAP = { ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', @@ -96,7 +110,6 @@ public class UserData { public int msgEncoding; public boolean msgEncodingSet = false; - // XXX needed when encoding is IS91 or DCS (not supported yet): public int msgType; /** diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java index 70d71fc..d1e4b4f 100755 --- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java +++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java @@ -448,11 +448,6 @@ public class GSMPhone extends PhoneBase { } public void - notifyMessageWaitingIndicator() { - mNotifier.notifyMessageWaitingChanged(this); - } - - public void notifyCallForwardingIndicator() { mNotifier.notifyCallForwardingChanged(this); } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 035c690..c33d4b6 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -65,6 +65,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { private static final String LOG_TAG = "GSM"; private static final boolean DBG = true; + private GSMPhone mGsmPhone; /** * Handles changes to the APN db. */ @@ -85,6 +86,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { // Indicates baseband will not auto-attach private boolean noAutoAttach = false; long nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + private boolean mReregisterOnReconnectFailure = false; private ContentResolver mResolver; private boolean mPingTestActive = false; @@ -204,6 +206,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { GsmDataConnectionTracker(GSMPhone p) { super(p); + mGsmPhone = p; p.mCM.registerForAvailable (this, EVENT_RADIO_AVAILABLE, null); p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null); @@ -250,16 +253,16 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { //Unregister for all events phone.mCM.unregisterForAvailable(this); phone.mCM.unregisterForOffOrNotAvailable(this); - ((GSMPhone) phone).mSIMRecords.unregisterForRecordsLoaded(this); + mGsmPhone.mSIMRecords.unregisterForRecordsLoaded(this); phone.mCM.unregisterForDataStateChanged(this); - ((GSMPhone) phone).mCT.unregisterForVoiceCallEnded(this); - ((GSMPhone) phone).mCT.unregisterForVoiceCallStarted(this); - ((GSMPhone) phone).mSST.unregisterForGprsAttached(this); - ((GSMPhone) phone).mSST.unregisterForGprsDetached(this); - ((GSMPhone) phone).mSST.unregisterForRoamingOn(this); - ((GSMPhone) phone).mSST.unregisterForRoamingOff(this); - ((GSMPhone) phone).mSST.unregisterForPsRestrictedEnabled(this); - ((GSMPhone) phone).mSST.unregisterForPsRestrictedDisabled(this); + mGsmPhone.mCT.unregisterForVoiceCallEnded(this); + mGsmPhone.mCT.unregisterForVoiceCallStarted(this); + mGsmPhone.mSST.unregisterForGprsAttached(this); + mGsmPhone.mSST.unregisterForGprsDetached(this); + mGsmPhone.mSST.unregisterForRoamingOn(this); + mGsmPhone.mSST.unregisterForRoamingOff(this); + mGsmPhone.mSST.unregisterForPsRestrictedEnabled(this); + mGsmPhone.mSST.unregisterForPsRestrictedDisabled(this); phone.getContext().unregisterReceiver(this.mIntentReceiver); phone.getContext().getContentResolver().unregisterContentObserver(this.apnObserver); @@ -374,10 +377,14 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { removeMessages(EVENT_RESTORE_DEFAULT_APN); setEnabled(type, false); if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) { + mRequestedApnType = Phone.APN_TYPE_DEFAULT; if (dataEnabled[APN_DEFAULT_ID]) { return Phone.APN_ALREADY_ACTIVE; } else { - cleanUpConnection(true, Phone.REASON_DATA_DISABLED); + Message msg = obtainMessage(EVENT_CLEAN_UP_CONNECTION); + msg.arg1 = 1; // tearDown is true; + msg.obj = Phone.REASON_DATA_DISABLED; + sendMessage(msg); return Phone.APN_REQUEST_STARTED; } } else { @@ -407,8 +414,8 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { public boolean isDataConnectionAsDesired() { boolean roaming = phone.getServiceState().getRoaming(); - if (((GSMPhone) phone).mSIMRecords.getRecordsLoaded() && - ((GSMPhone) phone).mSST.getCurrentGprsState() == ServiceState.STATE_IN_SERVICE && + if (mGsmPhone.mSIMRecords.getRecordsLoaded() && + mGsmPhone.mSST.getCurrentGprsState() == ServiceState.STATE_IN_SERVICE && (!roaming || getDataOnRoamingEnabled()) && !mIsWifiConnected && !mIsPsRestricted ) { @@ -572,13 +579,13 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { return true; } - int gprsState = ((GSMPhone) phone).mSST.getCurrentGprsState(); + int gprsState = mGsmPhone.mSST.getCurrentGprsState(); boolean roaming = phone.getServiceState().getRoaming(); - boolean desiredPowerState = ((GSMPhone) phone).mSST.getDesiredPowerState(); + boolean desiredPowerState = mGsmPhone.mSST.getDesiredPowerState(); if ((state == State.IDLE || state == State.SCANNING) && (gprsState == ServiceState.STATE_IN_SERVICE || noAutoAttach) - && ((GSMPhone) phone).mSIMRecords.getRecordsLoaded() + && mGsmPhone.mSIMRecords.getRecordsLoaded() && phone.getState() == Phone.State.IDLE && isDataAllowed() && !mIsPsRestricted @@ -604,8 +611,8 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { log("trySetupData: Not ready for data: " + " dataState=" + state + " gprsState=" + gprsState + - " sim=" + ((GSMPhone) phone).mSIMRecords.getRecordsLoaded() + - " UMTS=" + ((GSMPhone) phone).mSST.isConcurrentVoiceAndData() + + " sim=" + mGsmPhone.mSIMRecords.getRecordsLoaded() + + " UMTS=" + mGsmPhone.mSST.isConcurrentVoiceAndData() + " phoneState=" + phone.getState() + " dataEnabled=" + getAnyDataEnabled() + " roaming=" + roaming + @@ -792,7 +799,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { isConnected = (state != State.IDLE && state != State.FAILED); // The "current" may no longer be valid. MMS depends on this to send properly. - ((GSMPhone) phone).updateCurrentCarrierInProvider(); + mGsmPhone.updateCurrentCarrierInProvider(); // TODO: It'd be nice to only do this if the changed entrie(s) // match the current operator. @@ -802,6 +809,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { if (!isConnected) { // reset reconnect timer nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + mReregisterOnReconnectFailure = false; trySetupData(Phone.REASON_APN_CHANGED); } } @@ -882,6 +890,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { startNetStatPoll(); // reset reconnect timer nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + mReregisterOnReconnectFailure = false; } private void setupDnsProperties() { @@ -952,7 +961,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } else { mPdpResetCount = 0; EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_REREGISTER_NETWORK, sentSinceLastRecv); - ((GSMPhone) phone).mSST.reRegisterNetwork(null); + mGsmPhone.mSST.reRegisterNetwork(null); } // TODO: Add increasingly drastic recovery steps, eg, // reset the radio, reset the device. @@ -1166,6 +1175,20 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { private void reconnectAfterFail(FailCause lastFailCauseCode, String reason) { if (state == State.FAILED) { + if (nextReconnectDelay > RECONNECT_DELAY_MAX_MILLIS) { + if (mReregisterOnReconnectFailure) { + // We have already tried to re-register to the network. + // This might be a problem with the data network. + nextReconnectDelay = RECONNECT_DELAY_MAX_MILLIS; + } else { + // Try to Re-register to the network. + Log.d(LOG_TAG, "PDP activate failed, Reregistering to the network"); + mReregisterOnReconnectFailure = true; + mGsmPhone.mSST.reRegisterNetwork(null); + nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + return; + } + } Log.d(LOG_TAG, "PDP activate failed. Scheduling next attempt for " + (nextReconnectDelay / 1000) + "s"); @@ -1181,9 +1204,6 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { // double it for next time nextReconnectDelay *= 2; - if (nextReconnectDelay > RECONNECT_DELAY_MAX_MILLIS) { - nextReconnectDelay = RECONNECT_DELAY_MAX_MILLIS; - } if (!shouldPostNotification(lastFailCauseCode)) { Log.d(LOG_TAG,"NOT Posting GPRS Unavailable notification " @@ -1219,10 +1239,9 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { protected void onRestoreDefaultApn() { if (DBG) Log.d(LOG_TAG, "Restore default APN"); setEnabled(Phone.APN_TYPE_MMS, false); - + mRequestedApnType = Phone.APN_TYPE_DEFAULT; if (!isApnTypeActive(Phone.APN_TYPE_DEFAULT)) { cleanUpConnection(true, Phone.REASON_RESTORE_DEFAULT_APN); - mRequestedApnType = Phone.APN_TYPE_DEFAULT; } } @@ -1258,6 +1277,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { // Make sure our reconnect delay starts at the initial value // next time the radio comes on nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + mReregisterOnReconnectFailure = false; if (phone.getSimulatedRadioControl() != null) { // Assume data is connected on the simulator @@ -1370,7 +1390,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } protected void onVoiceCallStarted() { - if (state == State.CONNECTED && !((GSMPhone) phone).mSST.isConcurrentVoiceAndData()) { + if (state == State.CONNECTED && ! mGsmPhone.mSST.isConcurrentVoiceAndData()) { stopNetStatPoll(); phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED); } @@ -1378,7 +1398,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { protected void onVoiceCallEnded() { if (state == State.CONNECTED) { - if (!((GSMPhone) phone).mSST.isConcurrentVoiceAndData()) { + if (!mGsmPhone.mSST.isConcurrentVoiceAndData()) { startNetStatPoll(); phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED); } else { @@ -1388,6 +1408,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } else { // reset reconnect timer nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + mReregisterOnReconnectFailure = false; // in case data setup was attempted when we were on a voice call trySetupData(Phone.REASON_VOICE_CALL_ENDED); } @@ -1417,7 +1438,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { */ private void createAllApnList() { allApns = new ArrayList<ApnSetting>(); - String operator = ((GSMPhone) phone).mSIMRecords.getSIMOperatorNumeric(); + String operator = mGsmPhone.mSIMRecords.getSIMOperatorNumeric(); if (operator != null) { String selection = "numeric = '" + operator + "'"; @@ -1459,7 +1480,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { DataConnection pdp; for (int i = 0; i < PDP_CONNECTION_POOL_SIZE; i++) { - pdp = new PdpConnection((GSMPhone) phone); + pdp = new PdpConnection(mGsmPhone); pdpList.add(pdp); } } @@ -1478,7 +1499,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { */ private ArrayList<ApnSetting> buildWaitingApns() { ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>(); - String operator = ((GSMPhone )phone).mSIMRecords.getSIMOperatorNumeric(); + String operator = mGsmPhone.mSIMRecords.getSIMOperatorNumeric(); if (mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) { if (canSetPreferApn && preferredApn != null) { @@ -1664,6 +1685,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { if (state == State.FAILED) { cleanUpConnection(false, Phone.REASON_PS_RESTRICT_ENABLED); nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; + mReregisterOnReconnectFailure = false; } trySetupData(Phone.REASON_PS_RESTRICT_ENABLED); } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index f87392a..2770ddc 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -78,24 +78,16 @@ final class GsmSMSDispatcher extends SMSDispatcher { } } } - - if (mCm != null) { - mCm.acknowledgeLastIncomingGsmSms(true, Intents.RESULT_SMS_HANDLED, null); - } + acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null); } - /** - * Dispatches an incoming SMS messages. - * - * @param sms the incoming message from the phone - */ - protected void dispatchMessage(SmsMessageBase smsb) { + /** {@inheritDoc} */ + protected int dispatchMessage(SmsMessageBase smsb) { // If sms is null, means there was a parsing error. - // TODO: Should NAK this. if (smsb == null) { - return; + return Intents.RESULT_SMS_GENERIC_ERROR; } SmsMessage sms = (SmsMessage) smsb; boolean handled = false; @@ -115,7 +107,9 @@ final class GsmSMSDispatcher extends SMSDispatcher { } } - if (handled) return; + if (handled) { + return Intents.RESULT_SMS_HANDLED; + } SmsHeader smsHeader = sms.getUserDataHeader(); // See if message is partial or port addressed. @@ -126,17 +120,19 @@ final class GsmSMSDispatcher extends SMSDispatcher { if (smsHeader != null && smsHeader.portAddrs != null) { if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) { - mWapPush.dispatchWapPdu(sms.getUserData()); + return mWapPush.dispatchWapPdu(sms.getUserData()); + } else { + // The message was sent to a port, so concoct a URI for it. + dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort); } - // The message was sent to a port, so concoct a URI for it. - dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort); } else { // Normal short and non-port-addressed message, dispatch it. dispatchPdus(pdus); } + return Activity.RESULT_OK; } else { // Process the message part. - processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs); + return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs); } } diff --git a/test-runner/android/test/MoreAsserts.java b/test-runner/android/test/MoreAsserts.java index 2e74644..9e0d018 100644 --- a/test-runner/android/test/MoreAsserts.java +++ b/test-runner/android/test/MoreAsserts.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.ArrayList; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -316,8 +317,11 @@ public final class MoreAsserts { */ public static void assertContentsInOrder( String message, Iterable<?> actual, Object... expected) { - Assert.assertEquals(message, - Arrays.asList(expected), Lists.newArrayList(actual)); + ArrayList actualList = new ArrayList(); + for (Object o : actual) { + actualList.add(o); + } + Assert.assertEquals(message, Arrays.asList(expected), actualList); } /** diff --git a/test-runner/android/test/RenamingDelegatingContext.java b/test-runner/android/test/RenamingDelegatingContext.java index 3f64340..d780502 100644 --- a/test-runner/android/test/RenamingDelegatingContext.java +++ b/test-runner/android/test/RenamingDelegatingContext.java @@ -136,6 +136,11 @@ public class RenamingDelegatingContext extends ContextWrapper { return false; } } + + @Override + public File getDatabasePath(String name) { + return mFileContext.getDatabasePath(renamedFileName(name)); + } @Override public String[] databaseList() { diff --git a/tests/AndroidTests/src/com/android/unit_tests/BitwiseStreamsTest.java b/tests/AndroidTests/src/com/android/unit_tests/BitwiseStreamsTest.java index a935247..c5562b3 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/BitwiseStreamsTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/BitwiseStreamsTest.java @@ -25,6 +25,8 @@ import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; +import java.util.Random; + public class BitwiseStreamsTest extends AndroidTestCase { private final static String LOG_TAG = "BitwiseStreamsTest"; @@ -39,7 +41,7 @@ public class BitwiseStreamsTest extends AndroidTestCase { BitwiseInputStream inStream = new BitwiseInputStream(outBuf); byte[] inBufDup = new byte[inBuf.length]; inStream.skip(offset); - for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = inStream.read(8); + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); } @@ -53,7 +55,7 @@ public class BitwiseStreamsTest extends AndroidTestCase { BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); inStream.skip(offset); byte[] inBufDup = new byte[inBuf.length]; - for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = inStream.read(8); + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); } @@ -67,7 +69,7 @@ public class BitwiseStreamsTest extends AndroidTestCase { BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); inStream.skip(offset); byte[] inBufDup = new byte[inBuf.length]; - for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = inStream.read(8); + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); } @@ -84,12 +86,33 @@ public class BitwiseStreamsTest extends AndroidTestCase { BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); inStream.skip(offset); byte[] inBufDup = new byte[inBuf.length]; - for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = inStream.read(8); + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); } @SmallTest public void testFive() throws Exception { + Random random = new Random(); + int iterations = 10000; + int[] sizeArr = new int[iterations]; + int[] valueArr = new int[iterations]; + BitwiseOutputStream outStream = new BitwiseOutputStream(iterations * 4); + for (int i = 0; i < iterations; i++) { + int x = random.nextInt(); + int size = (x & 0x07) + 1; + int value = x & (-1 >>> (32 - size)); + sizeArr[i] = size; + valueArr[i] = value; + outStream.write(size, value); + } + BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); + for (int i = 0; i < iterations; i++) { + assertEquals(valueArr[i], inStream.read(sizeArr[i])); + } + } + + @SmallTest + public void testSix() throws Exception { int num_runs = 10; long start = android.os.SystemClock.elapsedRealtime(); for (int run = 0; run < num_runs; run++) { @@ -104,7 +127,7 @@ public class BitwiseStreamsTest extends AndroidTestCase { BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); inStream.skip(offset); byte[] inBufDup = new byte[inBuf.length]; - for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = inStream.read(8); + for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8); assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup)); } long end = android.os.SystemClock.elapsedRealtime(); diff --git a/tests/AndroidTests/src/com/android/unit_tests/CdmaSmsTest.java b/tests/AndroidTests/src/com/android/unit_tests/CdmaSmsTest.java index b2529811..2ff0a6a 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/CdmaSmsTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/CdmaSmsTest.java @@ -30,6 +30,8 @@ import android.test.suitebuilder.annotation.SmallTest; import java.util.Iterator; +import java.lang.Integer; + import android.util.Log; public class CdmaSmsTest extends AndroidTestCase { @@ -171,14 +173,14 @@ public class CdmaSmsTest extends AndroidTestCase { assertEquals(bearerData.msgCenterTimeStamp.minute, 1); assertEquals(bearerData.msgCenterTimeStamp.second, 59); assertEquals(bearerData.validityPeriodAbsolute, null); - assertEquals(bearerData.validityPeriodRelative, -63); + assertEquals(bearerData.validityPeriodRelative, 193); assertEquals(bearerData.deferredDeliveryTimeAbsolute.year, 1997); assertEquals(bearerData.deferredDeliveryTimeAbsolute.month, 5); assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthDay, 18); assertEquals(bearerData.deferredDeliveryTimeAbsolute.hour, 0); assertEquals(bearerData.deferredDeliveryTimeAbsolute.minute, 0); assertEquals(bearerData.deferredDeliveryTimeAbsolute.second, 0); - assertEquals(bearerData.deferredDeliveryTimeRelative, -57); + assertEquals(bearerData.deferredDeliveryTimeRelative, 199); assertEquals(bearerData.hasUserDataHeader, false); assertEquals(bearerData.userData.msgEncoding, UserData.ENCODING_7BIT_ASCII); assertEquals(bearerData.userData.numFields, 2); @@ -225,7 +227,7 @@ public class CdmaSmsTest extends AndroidTestCase { assertEquals(bearerData.deferredDeliveryTimeAbsolute.hour, 0); assertEquals(bearerData.deferredDeliveryTimeAbsolute.minute, 0); assertEquals(bearerData.deferredDeliveryTimeAbsolute.second, 0); - assertEquals(bearerData.deferredDeliveryTimeRelative, -110); + assertEquals(bearerData.deferredDeliveryTimeRelative, 146); assertEquals(bearerData.hasUserDataHeader, false); assertEquals(bearerData.userData.msgEncoding, UserData.ENCODING_7BIT_ASCII); assertEquals(bearerData.userData.numFields, 2); @@ -679,4 +681,15 @@ public class CdmaSmsTest extends AndroidTestCase { assertEquals(revBearerData.displayModeSet, true); assertEquals(revBearerData.displayMode, bearerData.displayMode); } + + @SmallTest + public void testIs91() throws Exception { + String pdu1 = "000320001001070c2039acc13880"; + BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1)); + assertEquals(bd1.callbackNumber.address, "3598271"); + String pdu4 = "000320001001080c283c314724b34e"; + BearerData bd4 = BearerData.decode(HexDump.hexStringToByteArray(pdu4)); + assertEquals(bd4.userData.payloadStr, "ABCDEFG"); + } + } diff --git a/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java index ae11ed1..ff73b32 100644 --- a/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java +++ b/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java @@ -93,6 +93,13 @@ public class PhoneNumberUtilsTest extends TestCase { assertEquals(b[i], bRet[i]); } + bRet = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength("+17005550020"); + assertEquals(8, bRet.length); + assertEquals(bRet[0], 7); + for (int i = 1; i < 8; i++) { + assertEquals(b[i - 1], bRet[i]); + } + bRet = PhoneNumberUtils.networkPortionToCalledPartyBCD("7005550020"); assertEquals("7005550020", PhoneNumberUtils.calledPartyBCDToString(bRet, 0, bRet.length)); diff --git a/tests/DpiTest/AndroidManifest.xml b/tests/DpiTest/AndroidManifest.xml index f71cff2..64ad7be 100644 --- a/tests/DpiTest/AndroidManifest.xml +++ b/tests/DpiTest/AndroidManifest.xml @@ -16,6 +16,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.google.android.test.dpi"> + <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="3" /> + <supports-screens android:smallScreens="true" /> <application android:label="DpiTest"> <activity android:name="DpiTestActivity" android:label="DpiTest"> <intent-filter> diff --git a/tests/DpiTest/res/values-largeScreen/strings.xml b/tests/DpiTest/res/values-largeScreen/strings.xml new file mode 100644 index 0000000..f4dd543 --- /dev/null +++ b/tests/DpiTest/res/values-largeScreen/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="act_title">DpiTest: Large Screen</string> +</resources> diff --git a/tests/DpiTest/res/values-normalScreen/strings.xml b/tests/DpiTest/res/values-normalScreen/strings.xml new file mode 100644 index 0000000..256d696 --- /dev/null +++ b/tests/DpiTest/res/values-normalScreen/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="act_title">DpiTest: Normal Screen</string> +</resources> diff --git a/tests/DpiTest/res/values-smallScreen/strings.xml b/tests/DpiTest/res/values-smallScreen/strings.xml new file mode 100644 index 0000000..cdb4ac9 --- /dev/null +++ b/tests/DpiTest/res/values-smallScreen/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="act_title">DpiTest: Small Screen</string> +</resources> diff --git a/tests/DpiTest/res/values/strings.xml b/tests/DpiTest/res/values/strings.xml new file mode 100644 index 0000000..ef924ac --- /dev/null +++ b/tests/DpiTest/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="act_title">DpiTest: Unknown Screen</string> +</resources> diff --git a/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java b/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java index 3759622..5a9f3f5 100644 --- a/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java +++ b/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java @@ -34,6 +34,7 @@ public class DpiTestActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + this.setTitle(R.string.act_title); LinearLayout root = new LinearLayout(this); root.setOrientation(LinearLayout.VERTICAL); diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java index 0218317..e741177 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java @@ -23,7 +23,9 @@ import java.util.List; import java.util.Map; import java.io.File; +import android.app.AlertDialog; import android.app.ListActivity; +import android.content.DialogInterface; import android.view.KeyEvent; import android.view.View; import android.widget.ListView; @@ -31,7 +33,7 @@ import android.widget.SimpleAdapter; import android.os.Bundle; -public abstract class FileList extends ListActivity +public abstract class FileList extends ListActivity { public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) @@ -39,7 +41,7 @@ public abstract class FileList extends ListActivity case KeyEvent.KEYCODE_DPAD_LEFT: if (mPath.length() > mBaseLength) { File f = new File(mPath); - mFocusFile = f.getName(); + mFocusFile = f.getName(); mFocusIndex = 0; f = f.getParentFile(); mPath = f.getPath(); @@ -47,7 +49,7 @@ public abstract class FileList extends ListActivity return true; } break; - + case KeyEvent.KEYCODE_DPAD_RIGHT: { Map map = (Map) getListView().getItemAtPosition(getListView().getSelectedItemPosition()); @@ -61,24 +63,24 @@ public abstract class FileList extends ListActivity } return true; } - + default: break; } return super.onKeyDown(keyCode, event); } - public void onCreate(Bundle icicle) + public void onCreate(Bundle icicle) { super.onCreate(icicle); setupPath(); updateList(); } - + protected List getData() { List myData = new ArrayList<HashMap>(); - + File f = new File(mPath); if (!f.exists()) { addItem(myData, "!LayoutTests path missing!", ""); @@ -103,10 +105,10 @@ public abstract class FileList extends ListActivity addItem(myData, files[i], path); } } - + return myData; } - + protected void addItem(List<Map> data, String name, String path) { HashMap temp = new HashMap(); @@ -114,34 +116,58 @@ public abstract class FileList extends ListActivity temp.put("path", path); data.add(temp); } - + protected void onListItemClick(ListView l, View v, int position, long id) { - Map map = (Map) l.getItemAtPosition(position); - String path = (String)map.get("path"); + Map map = (Map) l.getItemAtPosition(position); + final String path = (String)map.get("path"); if ((new File(path)).isDirectory()) { - mPath = path; - mFocusFile = null; - updateList(); + final CharSequence[] items = {"Open", "Run"}; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Select an Action"); + builder.setSingleChoiceItems(items, -1, + new DialogInterface.OnClickListener(){ + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case OPEN_DIRECTORY: + dialog.dismiss(); + mPath = path; + mFocusFile = null; + updateList(); + break; + case RUN_TESTS: + dialog.dismiss(); + processDirectory(path, false); + break; + } + } + }); + builder.create().show(); } else { processFile(path, false); } } - + + /* + * This function is called when the user has selected a directory in the + * list and wants to perform an action on it instead of navigating into + * the directory. + */ + abstract void processDirectory(String path, boolean selection); /* * This function is called when the user has selected a file in the * file list. The selected file could be a file or a directory. * The flag indicates if this was from a selection or not. */ abstract void processFile(String filename, boolean selection); - + /* * This function is called when the file list is being built. Return * true if the file is to be added to the file list. */ abstract boolean fileFilter(File f); - + protected void updateList() { setListAdapter(new SimpleAdapter(this, getData(), @@ -152,16 +178,19 @@ public abstract class FileList extends ListActivity setTitle(title); getListView().setSelection(mFocusIndex); } - - protected void setupPath() + + protected void setupPath() { mPath = "/sdcard/android/layout_tests"; mBaseLength = mPath.length(); } - + protected String mPath; protected int mBaseLength; protected String mFocusFile; protected int mFocusIndex; - + + private final static int OPEN_DIRECTORY = 0; + private final static int RUN_TESTS = 1; + } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java new file mode 100644 index 0000000..cc2f1f5 --- /dev/null +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java @@ -0,0 +1,80 @@ +package com.android.dumprendertree; + +import android.util.Log; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; + +public class FsUtils { + + private static final String LOGTAG = "FsUtils"; + private FsUtils() { + //no creation of instances + } + + public static void findLayoutTestsRecursively(BufferedOutputStream bos, + String dir) throws IOException { + Log.v(LOGTAG, "Searching tests under " + dir); + + File d = new File(dir); + if (!d.isDirectory()) { + throw new AssertionError("A directory expected, but got " + dir); + } + + String[] files = d.list(); + for (int i = 0; i < files.length; i++) { + String s = dir + "/" + files[i]; + if (FileFilter.ignoreTest(s)) { + Log.v(LOGTAG, " Ignoring: " + s); + continue; + } + if (s.toLowerCase().endsWith(".html") + || s.toLowerCase().endsWith(".xml")) { + bos.write(s.getBytes()); + bos.write('\n'); + continue; + } + + File f = new File(s); + if (f.isDirectory()) { + findLayoutTestsRecursively(bos, s); + continue; + } + + Log.v(LOGTAG, "Skipping " + s); + } + } + + public static void updateTestStatus(String statusFile, String s) { + try { + BufferedOutputStream bos = new BufferedOutputStream( + new FileOutputStream(statusFile)); + bos.write(s.getBytes()); + bos.close(); + } catch (Exception e) { + Log.e(LOGTAG, "Cannot update file " + statusFile); + } + } + + public static String readTestStatus(String statusFile) { + // read out the test name it stopped last time. + String status = null; + File testStatusFile = new File(statusFile); + if(testStatusFile.exists()) { + try { + BufferedReader inReader = new BufferedReader( + new FileReader(testStatusFile)); + status = inReader.readLine(); + inReader.close(); + } catch (IOException e) { + Log.e(LOGTAG, "Error reading test status.", e); + } + } + return status; + } + +} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java index f169a26..a03490d 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java @@ -178,15 +178,13 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh private void resumeTestList() { // read out the test name it stoped last time. try { - BufferedReader inReader = new BufferedReader(new FileReader(TEST_STATUS_FILE)); - String line = inReader.readLine(); + String line = FsUtils.readTestStatus(TEST_STATUS_FILE); for (int i = 0; i < mTestList.size(); i++) { if (mTestList.elementAt(i).equals(line)) { mTestList = new Vector<String>(mTestList.subList(i+1, mTestList.size())); break; } } - inReader.close(); } catch (Exception e) { Log.e(LOGTAG, "Error reading " + TEST_STATUS_FILE); } @@ -204,18 +202,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE + " : " + e.getMessage()); } } - - private void updateTestStatus(String s) { - // Write TEST_STATUS_FILE - try { - BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TEST_STATUS_FILE)); - bos.write(s.getBytes()); - bos.close(); - } catch (Exception e) { - Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE); - } - } - + private String getResultFile(String test) { String shortName = test.substring(0, test.lastIndexOf('.')); // Write actual results to result directory. @@ -392,12 +379,12 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh // Run tests. for (int i = 0; i < mTestList.size(); i++) { String s = mTestList.elementAt(i); - updateTestStatus(s); + FsUtils.updateTestStatus(TEST_STATUS_FILE, s); // Run tests runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis); } - updateTestStatus("#DONE"); + FsUtils.updateTestStatus(TEST_STATUS_FILE, "#DONE"); activity.finish(); } @@ -424,7 +411,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh try { File tests_list = new File(LAYOUT_TESTS_LIST_FILE); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false)); - findTestsRecursively(bos, getTestPath()); + FsUtils.findLayoutTestsRecursively(bos, getTestPath()); bos.flush(); bos.close(); } catch (Exception e) { @@ -432,38 +419,6 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh } } - private void findTestsRecursively(BufferedOutputStream bos, String dir) throws IOException { - Log.v(LOGTAG, "Searching tests under " + dir); - - File d = new File(dir); - if (!d.isDirectory()) { - throw new AssertionError("A directory expected, but got " + dir); - } - - String[] files = d.list(); - for (int i = 0; i < files.length; i++) { - String s = dir + "/" + files[i]; - if (FileFilter.ignoreTest(s)) { - Log.v(LOGTAG, " Ignoring: " + s); - continue; - } - if (s.toLowerCase().endsWith(".html") - || s.toLowerCase().endsWith(".xml")) { - bos.write(s.getBytes()); - bos.write('\n'); - continue; - } - - File f = new File(s); - if (f.isDirectory()) { - findTestsRecursively(bos, s); - continue; - } - - Log.v(LOGTAG, "Skipping " + s); - } - } - // Running all the layout tests at once sometimes // causes the dumprendertree to run out of memory. // So, additional tests are added to run the tests diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java index c792e8e..cbcac6c 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java @@ -16,18 +16,15 @@ package com.android.dumprendertree; +import dalvik.system.VMRuntime; + import android.app.Instrumentation; import android.content.Intent; - -import android.util.Log; - import android.os.Bundle; import android.os.Debug; -import android.os.Debug.MemoryInfo; +import android.os.Process; import android.test.ActivityInstrumentationTestCase2; - -import com.android.dumprendertree.TestShellActivity; -import com.android.dumprendertree.TestShellCallback; +import android.util.Log; import java.io.FileOutputStream; import java.io.IOException; @@ -70,9 +67,7 @@ public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShel TestShellActivity activity = (TestShellActivity) getActivity(); Log.v(LOGTAG, "About to run tests, calling gc first..."); - Runtime.getRuntime().runFinalization(); - Runtime.getRuntime().gc(); - Runtime.getRuntime().gc(); + freeMem(); // Run tests runTestAndWaitUntilDone(activity, runner.mTestPath, runner.mTimeoutInMillis); @@ -83,46 +78,73 @@ public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShel activity.finish(); } + private void freeMem() { + Log.v(LOGTAG, "freeMem: calling gc/finalization..."); + final VMRuntime runtime = VMRuntime.getRuntime(); + + runtime.gcSoftReferences(); + runtime.runFinalizationSync(); + runtime.gcSoftReferences(); + runtime.runFinalizationSync(); + runtime.gcSoftReferences(); + runtime.runFinalizationSync(); + Runtime.getRuntime().runFinalization(); + Runtime.getRuntime().gc(); + Runtime.getRuntime().gc(); + + } + + private void printRow(PrintStream ps, String format, Object...objs) { + ps.println(String.format(format, objs)); + } + private void dumpMemoryInfo() { try { - Log.v(LOGTAG, "About to dump meminfo, calling gc first..."); - Runtime.getRuntime().runFinalization(); - Runtime.getRuntime().gc(); - Runtime.getRuntime().gc(); - + freeMem(); Log.v(LOGTAG, "Dumping memory information."); FileOutputStream out = new FileOutputStream(LOAD_TEST_RESULT, true); PrintStream ps = new PrintStream(out); - MemoryInfo mi = new MemoryInfo(); - Debug.getMemoryInfo(mi); - - //try to fake the dumpsys format - //this will eventually be changed to XML - String format = "%15s:%9d%9d%9d%9d"; - String pss = - String.format(format, "(Pss)", - mi.nativePss, mi.dalvikPss, mi.otherPss, - mi.nativePss + mi.dalvikPss + mi.otherPss); - String sd = - String.format(format, "(shared dirty)", - mi.nativeSharedDirty, mi.dalvikSharedDirty, mi.otherSharedDirty, - mi.nativeSharedDirty + mi.dalvikSharedDirty + mi.otherSharedDirty); - String pd = - String.format(format, "(priv dirty)", - mi.nativePrivateDirty, mi.dalvikPrivateDirty, mi.otherPrivateDirty, - mi.nativePrivateDirty + mi.dalvikPrivateDirty + mi.otherPrivateDirty); - ps.print("\n\n\n"); - ps.println("** MEMINFO in pid 0 [com.android.dumprendertree] **"); - ps.println(" native dalvik other total"); - ps.println(" size: 12060 5255 N/A 17315"); - ps.println(" allocated: 12060 5255 N/A 17315"); - ps.println(" free: 12060 5255 N/A 17315"); - ps.println(pss); - ps.println(sd); - ps.println(pd); + ps.println("** MEMINFO in pid " + Process.myPid() + + " [com.android.dumprendertree] **"); + String formatString = "%17s %8s %8s %8s %8s"; + + long nativeMax = Debug.getNativeHeapSize() / 1024; + long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; + long nativeFree = Debug.getNativeHeapFreeSize() / 1024; + Runtime runtime = Runtime.getRuntime(); + long dalvikMax = runtime.totalMemory() / 1024; + long dalvikFree = runtime.freeMemory() / 1024; + long dalvikAllocated = dalvikMax - dalvikFree; + + + Debug.MemoryInfo memInfo = new Debug.MemoryInfo(); + Debug.getMemoryInfo(memInfo); + + final int nativeShared = memInfo.nativeSharedDirty; + final int dalvikShared = memInfo.dalvikSharedDirty; + final int otherShared = memInfo.otherSharedDirty; + + final int nativePrivate = memInfo.nativePrivateDirty; + final int dalvikPrivate = memInfo.dalvikPrivateDirty; + final int otherPrivate = memInfo.otherPrivateDirty; + + printRow(ps, formatString, "", "native", "dalvik", "other", "total"); + printRow(ps, formatString, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax); + printRow(ps, formatString, "allocated:", nativeAllocated, dalvikAllocated, "N/A", + nativeAllocated + dalvikAllocated); + printRow(ps, formatString, "free:", nativeFree, dalvikFree, "N/A", + nativeFree + dalvikFree); + + printRow(ps, formatString, "(Pss):", memInfo.nativePss, memInfo.dalvikPss, + memInfo.otherPss, memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); + + printRow(ps, formatString, "(shared dirty):", nativeShared, dalvikShared, otherShared, + nativeShared + dalvikShared + otherShared); + printRow(ps, formatString, "(priv dirty):", nativePrivate, dalvikPrivate, otherPrivate, + nativePrivate + dalvikPrivate + otherPrivate); ps.print("\n\n\n"); ps.flush(); ps.close(); @@ -142,7 +164,7 @@ public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShel LoadTestsAutoTest.this.notifyAll(); } } - + public void timedOut(String url) { } }); diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java b/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java index 00e0f89..e15ab65 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java @@ -17,19 +17,23 @@ package com.android.dumprendertree; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.util.Log; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileOutputStream; public class Menu extends FileList { - - public void onCreate(Bundle icicle) - { + + private static final int MENU_START = 0x01; + private static String LOGTAG = "MenuActivity"; + static final String LAYOUT_TESTS_LIST_FILE = "/sdcard/android/layout_tests_list.txt"; + + public void onCreate(Bundle icicle) { super.onCreate(icicle); } - + boolean fileFilter(File f) { if (f.getName().startsWith(".")) return false; @@ -41,14 +45,36 @@ public class Menu extends FileList { return true; return false; } - - void processFile(String filename, boolean selection) - { + + void processFile(String filename, boolean selection) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setClass(this, TestShellActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.putExtra(TestShellActivity.TEST_URL, "file://" + filename); startActivity(intent); } + + @Override + void processDirectory(String path, boolean selection) { + generateTestList(path); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setClass(this, TestShellActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(TestShellActivity.UI_AUTO_TEST, LAYOUT_TESTS_LIST_FILE); + startActivity(intent); + } + + private void generateTestList(String path) { + try { + File tests_list = new File(LAYOUT_TESTS_LIST_FILE); + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false)); + FsUtils.findLayoutTestsRecursively(bos, path); + bos.flush(); + bos.close(); + } catch (Exception e) { + Log.e(LOGTAG, "Error when creating test list: " + e.getMessage()); + } + } + } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java index 16973be..de39800 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java @@ -45,7 +45,7 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2<Reliabilit //always try to resume first, hence cleaning up status will be the //responsibility of driver scripts - String lastUrl = readTestStatus(); + String lastUrl = FsUtils.readTestStatus(TEST_STATUS_FILE); if(lastUrl != null && !TEST_DONE.equals(lastUrl)) fastForward(listReader, lastUrl); @@ -62,7 +62,7 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2<Reliabilit continue; start = System.currentTimeMillis(); Log.v(LOGTAG, "Testing URL: " + url); - updateTestStatus(url); + FsUtils.updateTestStatus(TEST_STATUS_FILE, url); activity.reset(); //use message to send new URL to avoid interacting with //WebView in non-UI thread @@ -92,7 +92,7 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2<Reliabilit System.gc(); System.gc(); } - updateTestStatus(TEST_DONE); + FsUtils.updateTestStatus(TEST_STATUS_FILE, TEST_DONE); activity.finish(); listReader.close(); } @@ -122,35 +122,6 @@ public class ReliabilityTest extends ActivityInstrumentationTestCase2<Reliabilit } } - private void updateTestStatus(String s) { - // write last tested url into status file - try { - BufferedOutputStream bos = new BufferedOutputStream( - new FileOutputStream(TEST_STATUS_FILE)); - bos.write(s.getBytes()); - bos.close(); - } catch (IOException e) { - Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE, e); - } - } - - private String readTestStatus() { - // read out the test name it stopped last time. - String status = null; - File testStatusFile = new File(TEST_STATUS_FILE); - if(testStatusFile.exists()) { - try { - BufferedReader inReader = new BufferedReader( - new FileReader(testStatusFile)); - status = inReader.readLine(); - inReader.close(); - } catch (IOException e) { - Log.e(LOGTAG, "Error reading test status.", e); - } - } - return status; - } - private void fastForward(BufferedReader testListReader, String lastUrl) { //fastforward the BufferedReader to the position right after last url if(lastUrl == null) diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java index 02658a1..1980ec2 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java @@ -17,7 +17,10 @@ package com.android.dumprendertree; import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; +import android.content.DialogInterface.OnClickListener; import android.graphics.Bitmap; import android.net.http.SslError; import android.os.Bundle; @@ -36,23 +39,26 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.LinearLayout; +import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Vector; public class TestShellActivity extends Activity implements LayoutTestController { - + static enum DumpDataType {DUMP_AS_TEXT, EXT_REPR, NO_OP} - + public class AsyncHandler extends Handler { @Override public void handleMessage(Message msg) { if (msg.what == MSG_TIMEOUT) { mTimedOut = true; - mCallback.timedOut(mWebView.getUrl()); + if(mCallback != null) + mCallback.timedOut(mWebView.getUrl()); requestWebKitData(); return; } else if (msg.what == MSG_WEBKIT_DATA) { @@ -66,10 +72,10 @@ public class TestShellActivity extends Activity implements LayoutTestController public void requestWebKitData() { Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA); - + if (mRequestedWebKitData) throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl()); - + mRequestedWebKitData = true; switch (mDumpDataType) { case DUMP_AS_TEXT: @@ -82,12 +88,12 @@ public class TestShellActivity extends Activity implements LayoutTestController finished(); break; } - } + } @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - + LinearLayout contentView = new LinearLayout(this); contentView.setOrientation(LinearLayout.VERTICAL); setContentView(contentView); @@ -99,57 +105,122 @@ public class TestShellActivity extends Activity implements LayoutTestController setupWebViewForLayoutTests(mWebView, mCallbackProxy); contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0f)); - + + mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); + mHandler = new AsyncHandler(); - + Intent intent = getIntent(); if (intent != null) { executeIntent(intent); } } - + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); executeIntent(intent); } - + private void executeIntent(Intent intent) { resetTestStatus(); if (!Intent.ACTION_VIEW.equals(intent.getAction())) { return; } - + mTestUrl = intent.getStringExtra(TEST_URL); - if (mTestUrl == null) + if (mTestUrl == null) { + mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST); + if(mUiAutoTestPath != null) { + beginUiAutoTest(); + } return; - + } + mResultFile = intent.getStringExtra(RESULT_FILE); mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0); Log.v(LOGTAG, " Loading " + mTestUrl); mWebView.loadUrl(mTestUrl); - + if (mTimeoutInMillis > 0) { // Create a timeout timer Message m = mHandler.obtainMessage(MSG_TIMEOUT); mHandler.sendMessageDelayed(m, mTimeoutInMillis); } } - + + private void beginUiAutoTest() { + try { + mTestListReader = new BufferedReader( + new FileReader(mUiAutoTestPath)); + } catch (IOException ioe) { + Log.e(LOGTAG, "Failed to open test list for read.", ioe); + finishUiAutoTest(); + return; + } + moveToNextTest(); + } + + private void finishUiAutoTest() { + try { + if(mTestListReader != null) + mTestListReader.close(); + } catch (IOException ioe) { + Log.w(LOGTAG, "Failed to close test list file.", ioe); + } + finished(); + } + + private void moveToNextTest() { + String url = null; + try { + url = mTestListReader.readLine(); + } catch (IOException ioe) { + Log.e(LOGTAG, "Failed to read next test.", ioe); + finishUiAutoTest(); + return; + } + if (url == null) { + mUiAutoTestPath = null; + finishUiAutoTest(); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage("All tests finished. Exit?") + .setCancelable(false) + .setPositiveButton("Yes", new OnClickListener(){ + public void onClick(DialogInterface dialog, int which) { + TestShellActivity.this.finish(); + } + }) + .setNegativeButton("No", new OnClickListener(){ + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + builder.create().show(); + return; + } + url = "file://" + url; + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(TestShellActivity.TEST_URL, url); + intent.putExtra(TIMEOUT_IN_MILLIS, 10000); + executeIntent(intent); + } + @Override protected void onStop() { super.onStop(); mWebView.stopLoading(); } - + @Override protected void onDestroy() { super.onDestroy(); mWebView.destroy(); mWebView = null; } - + @Override public void onLowMemory() { super.onLowMemory(); @@ -163,13 +234,13 @@ public class TestShellActivity extends Activity implements LayoutTestController finished(); return; } - + try { File parentDir = new File(mResultFile).getParentFile(); if (!parentDir.exists()) { parentDir.mkdirs(); } - + FileOutputStream os = new FileOutputStream(mResultFile); if (timeout) { Log.w("Layout test: Timeout", mResultFile); @@ -192,22 +263,27 @@ public class TestShellActivity extends Activity implements LayoutTestController os.flush(); os.close(); } catch (IOException ex) { - Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage()); + Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage()); } finished(); } - + public void setCallback(TestShellCallback callback) { mCallback = callback; } - + public void finished() { - if (mCallback != null) { - mCallback.finished(); + if (mUiAutoTestPath != null) { + //don't really finish here + moveToNextTest(); + } else { + if (mCallback != null) { + mCallback.finished(); + } } } - + public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) { mDefaultDumpDataType = defaultDumpDataType; } @@ -227,7 +303,7 @@ public class TestShellActivity extends Activity implements LayoutTestController String url = mWebView.getUrl(); Log.v(LOGTAG, "waitUntilDone called: " + url); } - + public void notifyDone() { String url = mWebView.getUrl(); Log.v(LOGTAG, "notifyDone called: " + url); @@ -236,7 +312,7 @@ public class TestShellActivity extends Activity implements LayoutTestController mChromeClient.onProgressChanged(mWebView, 100); } } - + public void display() { mWebView.invalidate(); } @@ -302,7 +378,7 @@ public class TestShellActivity extends Activity implements LayoutTestController } public void queueScript(String scriptToRunInCurrentContext) { - mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext); + mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext); } public void repaintSweepHorizontally() { @@ -421,7 +497,7 @@ public class TestShellActivity extends Activity implements LayoutTestController result.confirm(); return true; } - + @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { @@ -434,7 +510,7 @@ public class TestShellActivity extends Activity implements LayoutTestController result.confirm(); return true; } - + @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { @@ -516,7 +592,7 @@ public class TestShellActivity extends Activity implements LayoutTestController return true; } }; - + private void resetTestStatus() { mWaitUntilDone = false; mDumpDataType = mDefaultDumpDataType; @@ -555,10 +631,12 @@ public class TestShellActivity extends Activity implements LayoutTestController private TestShellCallback mCallback; private CallbackProxy mCallbackProxy; - + private String mTestUrl; private String mResultFile; private int mTimeoutInMillis; + private String mUiAutoTestPath; + private BufferedReader mTestListReader; // States private boolean mTimedOut; @@ -580,13 +658,14 @@ public class TestShellActivity extends Activity implements LayoutTestController private boolean mCanOpenWindows; static final String TIMEOUT_STR = "**Test timeout"; - + static final int MSG_TIMEOUT = 0; static final int MSG_WEBKIT_DATA = 1; static final String LOGTAG="TestShell"; - + static final String TEST_URL = "TestUrl"; static final String RESULT_FILE = "ResultFile"; static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis"; + static final String UI_AUTO_TEST = "UiAutoTest"; } diff --git a/tests/backup/src/com/android/backuptest/BackupTestAgent.java b/tests/backup/src/com/android/backuptest/BackupTestAgent.java index c6acc66..8e4fd39 100644 --- a/tests/backup/src/com/android/backuptest/BackupTestAgent.java +++ b/tests/backup/src/com/android/backuptest/BackupTestAgent.java @@ -18,11 +18,15 @@ package com.android.backuptest; import android.backup.BackupHelperAgent; import android.backup.FileBackupHelper; +import android.backup.SharedPreferencesBackupHelper; public class BackupTestAgent extends BackupHelperAgent { public void onCreate() { addHelper("data_files", new FileBackupHelper(this, BackupTestActivity.FILE_NAME)); + addHelper("more_data_files", new FileBackupHelper(this, "another_file.txt", "3.txt", + "empty.txt")); + addHelper("shared_prefs", new SharedPreferencesBackupHelper(this, "settings", "raw")); } } diff --git a/tests/backup/test_backup.sh b/tests/backup/test_backup.sh new file mode 100755 index 0000000..dbf9ed2 --- /dev/null +++ b/tests/backup/test_backup.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +#adb kill-server + +# set the transport +adb shell bmgr transport 1 + +# load up the three files +adb shell "rm /data/data/com.android.backuptest/files/* ; \ + mkdir /data/data/com.android.backuptest ; \ + mkdir /data/data/com.android.backuptest/files ; \ + mkdir /data/data/com.android.backuptest/shared_prefs ; \ + echo -n \"<map><int name=\\\"pref\\\" value=\\\"1\\\" /></map>\" > /data/data/com.android.backuptest/shared_prefs/raw.xml ; \ + echo -n first file > /data/data/com.android.backuptest/files/file.txt ; \ + echo -n asdf > /data/data/com.android.backuptest/files/another_file.txt ; \ + echo -n 3 > /data/data/com.android.backuptest/files/3.txt ; \ + echo -n "" > /data/data/com.android.backuptest/files/empty.txt ; \ +" + +# say that the data has changed +adb shell bmgr backup com.android.backuptest + +# run the backup +adb shell bmgr run + + + diff --git a/tests/backup/test_restore.sh b/tests/backup/test_restore.sh new file mode 100755 index 0000000..ccf29cf --- /dev/null +++ b/tests/backup/test_restore.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +function check_file +{ + data=$(adb shell cat /data/data/com.android.backuptest/$1) + if [ "$data" = "$2" ] ; then + echo "$1 has correct value [$2]" + else + echo $1 is INCORRECT + echo " value: [$data]" + echo " expected: [$2]" + fi +} + +# delete the old data +echo --- Previous files +adb shell "ls -l /data/data/com.android.backuptest/files" +adb shell "rm /data/data/com.android.backuptest/files/*" +echo --- Previous shared_prefs +adb shell "ls -l /data/data/com.android.backuptest/shared_prefs" +adb shell "rm /data/data/com.android.backuptest/shared_prefs/*" +echo --- Erased files and shared_prefs +adb shell "ls -l /data/data/com.android.backuptest/files" +adb shell "ls -l /data/data/com.android.backuptest/shared_prefs" +echo --- + +echo +echo +echo + +# run the restore +adb shell bmgr restore 0 + +echo +echo +echo + +# check the results +check_file files/file.txt "first file" +check_file files/another_file.txt "asdf" +check_file files/3.txt "3" +check_file files/empty.txt "" +check_file shared_prefs/raw.xml '<map><int name="pref" value="1" /></map>' + +echo +echo +echo +echo --- Restored files +adb shell "ls -l /data/data/com.android.backuptest/files" +echo --- Restored shared_prefs +adb shell "ls -l /data/data/com.android.backuptest/shared_prefs" +echo --- +echo diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index 6bc1ee6..67af116 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -187,6 +187,13 @@ AaptGroupEntry::parseNamePart(const String8& part, int* axis, uint32_t* value) return 0; } + // screen layout + if (getScreenLayoutName(part.string(), &config)) { + *axis = AXIS_SCREENLAYOUT; + *value = config.screenLayout; + return 0; + } + // version if (getVersionName(part.string(), &config)) { *axis = AXIS_VERSION; @@ -202,7 +209,7 @@ AaptGroupEntry::initFromDirName(const char* dir, String8* resType) { Vector<String8> parts; - String8 mcc, mnc, loc, orient, den, touch, key, keysHidden, nav, size, vers; + String8 mcc, mnc, loc, orient, den, touch, key, keysHidden, nav, size, layout, vers; const char *p = dir; const char *q; @@ -378,6 +385,18 @@ AaptGroupEntry::initFromDirName(const char* dir, String8* resType) //printf("not screen size: %s\n", part.string()); } + if (getScreenLayoutName(part.string())) { + layout = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not screen layout: %s\n", part.string()); + } + if (getVersionName(part.string())) { vers = part; @@ -404,6 +423,7 @@ success: this->keyboard = key; this->navigation = nav; this->screenSize = size; + this->screenLayout = layout; this->version = vers; // what is this anyway? @@ -435,6 +455,8 @@ AaptGroupEntry::toString() const s += ","; s += screenSize; s += ","; + s += screenLayout; + s += ","; s += version; return s; } @@ -483,6 +505,10 @@ AaptGroupEntry::toDirName(const String8& resType) const s += "-"; s += screenSize; } + if (this->screenLayout != "") { + s += "-"; + s += screenLayout; + } if (this->version != "") { s += "-"; s += version; @@ -786,6 +812,26 @@ bool AaptGroupEntry::getScreenSizeName(const char* name, return true; } +bool AaptGroupEntry::getScreenLayoutName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = out->SCREENLAYOUT_ANY; + return true; + } else if (strcmp(name, "smallscreen") == 0) { + if (out) out->screenLayout = out->SCREENLAYOUT_SMALL; + return true; + } else if (strcmp(name, "normalscreen") == 0) { + if (out) out->screenLayout = out->SCREENLAYOUT_NORMAL; + return true; + } else if (strcmp(name, "largescreen") == 0) { + if (out) out->screenLayout = out->SCREENLAYOUT_LARGE; + return true; + } + + return false; +} + bool AaptGroupEntry::getVersionName(const char* name, ResTable_config* out) { @@ -828,6 +874,7 @@ int AaptGroupEntry::compare(const AaptGroupEntry& o) const if (v == 0) v = keyboard.compare(o.keyboard); if (v == 0) v = navigation.compare(o.navigation); if (v == 0) v = screenSize.compare(o.screenSize); + if (v == 0) v = screenLayout.compare(o.screenLayout); if (v == 0) v = version.compare(o.version); return v; } @@ -846,6 +893,7 @@ ResTable_config AaptGroupEntry::toParams() const getKeyboardName(keyboard.string(), ¶ms); getNavigationName(navigation.string(), ¶ms); getScreenSizeName(screenSize.string(), ¶ms); + getScreenLayoutName(screenLayout.string(), ¶ms); getVersionName(version.string(), ¶ms); return params; } diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 3f37069..e8c7395 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -37,6 +37,7 @@ enum { AXIS_KEYBOARD, AXIS_NAVIGATION, AXIS_SCREENSIZE, + AXIS_SCREENLAYOUT, AXIS_VERSION }; @@ -62,6 +63,7 @@ public: String8 keyboard; String8 navigation; String8 screenSize; + String8 screenLayout; String8 version; bool initFromDirName(const char* dir, String8* resType); @@ -78,6 +80,7 @@ public: static bool getKeyboardName(const char* name, ResTable_config* out = NULL); static bool getNavigationName(const char* name, ResTable_config* out = NULL); static bool getScreenSizeName(const char* name, ResTable_config* out = NULL); + static bool getScreenLayoutName(const char* name, ResTable_config* out = NULL); static bool getVersionName(const char* name, ResTable_config* out = NULL); int compare(const AaptGroupEntry& o) const; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index dcda379..c0ae592 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -331,6 +331,9 @@ enum { TARGET_SDK_VERSION_ATTR = 0x01010270, TEST_ONLY_ATTR = 0x01010272, DENSITY_ATTR = 0x0101026c, + SMALL_SCREEN_ATTR = 0x01010284, + NORMAL_SCREEN_ATTR = 0x01010285, + LARGE_SCREEN_ATTR = 0x01010286, }; const char *getComponentName(String8 &pkgName, String8 &componentName) { @@ -501,6 +504,10 @@ int doDump(Bundle* bundle) bool isLauncherActivity = false; bool withinApplication = false; bool withinReceiver = false; + int targetSdk = 0; + int smallScreen = 1; + int normalScreen = 1; + int largeScreen = 1; String8 pkg; String8 activityName; String8 activityLabel; @@ -574,8 +581,10 @@ int doDump(Bundle* bundle) error.string()); goto bail; } + if (name == "Donut") targetSdk = 4; printf("sdkVersion:'%s'\n", name.string()); } else if (code != -1) { + targetSdk = code; printf("sdkVersion:'%d'\n", code); } code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error); @@ -587,8 +596,12 @@ int doDump(Bundle* bundle) error.string()); goto bail; } + if (name == "Donut" && targetSdk < 4) targetSdk = 4; printf("targetSdkVersion:'%s'\n", name.string()); } else if (code != -1) { + if (targetSdk < code) { + targetSdk = code; + } printf("targetSdkVersion:'%d'\n", code); } } else if (tag == "uses-configuration") { @@ -627,6 +640,13 @@ int doDump(Bundle* bundle) goto bail; } printf("supports-density:'%d'\n", dens); + } else if (tag == "supports-screens") { + smallScreen = getIntegerAttribute(tree, + SMALL_SCREEN_ATTR, NULL, 1); + normalScreen = getIntegerAttribute(tree, + NORMAL_SCREEN_ATTR, NULL, 1); + largeScreen = getIntegerAttribute(tree, + LARGE_SCREEN_ATTR, NULL, 1); } } else if (depth == 3 && withinApplication) { withinActivity = false; @@ -735,6 +755,25 @@ int doDump(Bundle* bundle) } } + // Determine default values for any unspecified screen sizes, + // based on the target SDK of the package. As of 4 (donut) + // the screen size support was introduced, so all default to + // enabled. + if (smallScreen > 0) { + smallScreen = targetSdk >= 4 ? -1 : 0; + } + if (normalScreen > 0) { + normalScreen = -1; + } + if (largeScreen > 0) { + largeScreen = targetSdk >= 4 ? -1 : 0; + } + printf("supports-screens:"); + if (smallScreen != 0) printf(" 'small'"); + if (normalScreen != 0) printf(" 'normal'"); + if (largeScreen != 0) printf(" 'large'"); + printf("\n"); + printf("locales:"); Vector<String8> locales; res.getLocales(&locales); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java index 1fa11af..06dd96f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java @@ -59,7 +59,7 @@ public class BridgeAssetManager extends AssetManager { public void setConfiguration(int mcc, int mnc, String locale, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, - int version) { + int screenLayout, int version) { Configuration c = new Configuration(); c.mcc = mcc; @@ -70,5 +70,6 @@ public class BridgeAssetManager extends AssetManager { c.keyboardHidden = keyboardHidden; c.navigation = navigation; c.orientation = orientation; + c.screenLayout = screenLayout; } } diff --git a/tools/localize/Perforce.cpp b/tools/localize/Perforce.cpp index a7f301e..ae11231 100644 --- a/tools/localize/Perforce.cpp +++ b/tools/localize/Perforce.cpp @@ -1,6 +1,7 @@ #include "Perforce.h" #include "log.h" #include <string.h> +#include <cstdio> #include <stdlib.h> #include <sstream> #include <sys/types.h> diff --git a/tools/localize/SourcePos.cpp b/tools/localize/SourcePos.cpp index dd54f3a..184bfe0 100644 --- a/tools/localize/SourcePos.cpp +++ b/tools/localize/SourcePos.cpp @@ -1,6 +1,7 @@ #include "SourcePos.h" #include <stdarg.h> +#include <cstdio> #include <set> #include <cstdio> diff --git a/tools/localize/file_utils.cpp b/tools/localize/file_utils.cpp index 84081f5..775ce2f 100644 --- a/tools/localize/file_utils.cpp +++ b/tools/localize/file_utils.cpp @@ -1,4 +1,5 @@ #include <string.h> +#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "file_utils.h" diff --git a/tools/localize/localize_test.cpp b/tools/localize/localize_test.cpp index 931ea95..1d0ac9a 100644 --- a/tools/localize/localize_test.cpp +++ b/tools/localize/localize_test.cpp @@ -2,6 +2,7 @@ #include "XLIFFFile.h" #include "ValuesFile.h" #include "localize.h" +#include <stdio.h> int pseudolocalize_xliff(XLIFFFile* xliff, bool expand); diff --git a/tools/localize/merge_res_and_xliff.cpp b/tools/localize/merge_res_and_xliff.cpp index 58a6554..1fdaa0e 100644 --- a/tools/localize/merge_res_and_xliff.cpp +++ b/tools/localize/merge_res_and_xliff.cpp @@ -3,6 +3,7 @@ #include "file_utils.h" #include "Perforce.h" #include "log.h" +#include <stdio.h> static set<StringResource>::const_iterator find_id(const set<StringResource>& s, const string& id, int index) diff --git a/tools/localize/merge_res_and_xliff_test.cpp b/tools/localize/merge_res_and_xliff_test.cpp index f638a74..6fe2629 100644 --- a/tools/localize/merge_res_and_xliff_test.cpp +++ b/tools/localize/merge_res_and_xliff_test.cpp @@ -1,6 +1,6 @@ #include <cstdio> #include "merge_res_and_xliff.h" - +#include <stdio.h> int merge_test() diff --git a/tools/localize/xmb.cpp b/tools/localize/xmb.cpp index 236705f..d8f6ff0 100644 --- a/tools/localize/xmb.cpp +++ b/tools/localize/xmb.cpp @@ -7,6 +7,7 @@ #include "XLIFFFile.h" #include <map> +#include <cstdio> using namespace std; diff --git a/vpn/java/android/net/vpn/IVpnService.aidl b/vpn/java/android/net/vpn/IVpnService.aidl index 0e658df..fedccb0 100644 --- a/vpn/java/android/net/vpn/IVpnService.aidl +++ b/vpn/java/android/net/vpn/IVpnService.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/vpn/java/android/net/vpn/L2tpIpsecProfile.java b/vpn/java/android/net/vpn/L2tpIpsecProfile.java index 181619d..4ae2dec 100644 --- a/vpn/java/android/net/vpn/L2tpIpsecProfile.java +++ b/vpn/java/android/net/vpn/L2tpIpsecProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. @@ -19,15 +19,14 @@ package android.net.vpn; import android.os.Parcel; /** - * The profile for L2TP-over-IPSec type of VPN. + * The profile for certificate-based L2TP-over-IPSec type of VPN. * {@hide} */ -public class L2tpIpsecProfile extends VpnProfile { +public class L2tpIpsecProfile extends L2tpProfile { private static final long serialVersionUID = 1L; private String mUserCertificate; private String mCaCertificate; - private String mUserkey; @Override public VpnType getType() { @@ -50,20 +49,11 @@ public class L2tpIpsecProfile extends VpnProfile { return mUserCertificate; } - public void setUserkey(String name) { - mUserkey = name; - } - - public String getUserkey() { - return mUserkey; - } - @Override protected void readFromParcel(Parcel in) { super.readFromParcel(in); mCaCertificate = in.readString(); mUserCertificate = in.readString(); - mUserkey = in.readString(); } @Override @@ -71,6 +61,5 @@ public class L2tpIpsecProfile extends VpnProfile { super.writeToParcel(parcel, flags); parcel.writeString(mCaCertificate); parcel.writeString(mUserCertificate); - parcel.writeString(mUserkey); } } diff --git a/vpn/java/android/net/vpn/L2tpIpsecPskProfile.java b/vpn/java/android/net/vpn/L2tpIpsecPskProfile.java new file mode 100644 index 0000000..7a03018 --- /dev/null +++ b/vpn/java/android/net/vpn/L2tpIpsecPskProfile.java @@ -0,0 +1,54 @@ +/* + * 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.net.vpn; + +import android.os.Parcel; + +/** + * The profile for pre-shared-key-based L2TP-over-IPSec type of VPN. + * {@hide} + */ +public class L2tpIpsecPskProfile extends L2tpProfile { + private static final long serialVersionUID = 1L; + + private String mPresharedKey; + + @Override + public VpnType getType() { + return VpnType.L2TP_IPSEC_PSK; + } + + public void setPresharedKey(String key) { + mPresharedKey = key; + } + + public String getPresharedKey() { + return mPresharedKey; + } + + @Override + protected void readFromParcel(Parcel in) { + super.readFromParcel(in); + mPresharedKey = in.readString(); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + super.writeToParcel(parcel, flags); + parcel.writeString(mPresharedKey); + } +} diff --git a/vpn/java/android/net/vpn/L2tpProfile.java b/vpn/java/android/net/vpn/L2tpProfile.java index 59d4981..dbba0c5 100644 --- a/vpn/java/android/net/vpn/L2tpProfile.java +++ b/vpn/java/android/net/vpn/L2tpProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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,6 +16,8 @@ package android.net.vpn; +import android.os.Parcel; + /** * The profile for L2TP type of VPN. * {@hide} @@ -23,8 +25,44 @@ package android.net.vpn; public class L2tpProfile extends VpnProfile { private static final long serialVersionUID = 1L; + private boolean mSecret; + private String mSecretString; + @Override public VpnType getType() { return VpnType.L2TP; } + + /** + * Enables/disables the secret for authenticating tunnel connection. + */ + public void setSecretEnabled(boolean enabled) { + mSecret = enabled; + } + + public boolean isSecretEnabled() { + return mSecret; + } + + public void setSecretString(String secret) { + mSecretString = secret; + } + + public String getSecretString() { + return mSecretString; + } + + @Override + protected void readFromParcel(Parcel in) { + super.readFromParcel(in); + mSecret = in.readInt() > 0; + mSecretString = in.readString(); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + super.writeToParcel(parcel, flags); + parcel.writeInt(mSecret ? 1 : 0); + parcel.writeString(mSecretString); + } } diff --git a/vpn/java/android/net/vpn/PptpProfile.java b/vpn/java/android/net/vpn/PptpProfile.java new file mode 100644 index 0000000..c68bb71 --- /dev/null +++ b/vpn/java/android/net/vpn/PptpProfile.java @@ -0,0 +1,30 @@ +/* + * 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.net.vpn; + +/** + * The profile for PPTP type of VPN. + * {@hide} + */ +public class PptpProfile extends VpnProfile { + private static final long serialVersionUID = 1L; + + @Override + public VpnType getType() { + return VpnType.PPTP; + } +} diff --git a/vpn/java/android/net/vpn/VpnManager.java b/vpn/java/android/net/vpn/VpnManager.java index 98795bd..dc70b26 100644 --- a/vpn/java/android/net/vpn/VpnManager.java +++ b/vpn/java/android/net/vpn/VpnManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/vpn/java/android/net/vpn/VpnProfile.aidl b/vpn/java/android/net/vpn/VpnProfile.aidl index ad34bfc..edeaef0 100644 --- a/vpn/java/android/net/vpn/VpnProfile.aidl +++ b/vpn/java/android/net/vpn/VpnProfile.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/vpn/java/android/net/vpn/VpnProfile.java b/vpn/java/android/net/vpn/VpnProfile.java index 9e24da4..bd6c809 100644 --- a/vpn/java/android/net/vpn/VpnProfile.java +++ b/vpn/java/android/net/vpn/VpnProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/vpn/java/android/net/vpn/VpnState.java b/vpn/java/android/net/vpn/VpnState.java index 977d938..ebd9364 100644 --- a/vpn/java/android/net/vpn/VpnState.java +++ b/vpn/java/android/net/vpn/VpnState.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. diff --git a/vpn/java/android/net/vpn/VpnType.java b/vpn/java/android/net/vpn/VpnType.java index 91b0ea2..c7df943 100644 --- a/vpn/java/android/net/vpn/VpnType.java +++ b/vpn/java/android/net/vpn/VpnType.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 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. @@ -21,14 +21,21 @@ package android.net.vpn; * {@hide} */ public enum VpnType { - L2TP_IPSEC("L2TP/IPSec", L2tpIpsecProfile.class), - L2TP("L2TP", L2tpProfile.class); + PPTP("PPTP", "", PptpProfile.class), + L2TP("L2TP", "", L2tpProfile.class), + L2TP_IPSEC_PSK("L2TP/IPSec PSK", "Pre-shared key based L2TP/IPSec VPN", + L2tpIpsecPskProfile.class), + L2TP_IPSEC("L2TP/IPSec CRT", "Certificate based L2TP/IPSec VPN", + L2tpIpsecProfile.class); private String mDisplayName; + private String mDescription; private Class<? extends VpnProfile> mClass; - VpnType(String displayName, Class<? extends VpnProfile> klass) { + VpnType(String displayName, String description, + Class<? extends VpnProfile> klass) { mDisplayName = displayName; + mDescription = description; mClass = klass; } @@ -36,6 +43,10 @@ public enum VpnType { return mDisplayName; } + public String getDescription() { + return mDescription; + } + public Class<? extends VpnProfile> getProfileClass() { return mClass; } |