diff options
33 files changed, 483 insertions, 186 deletions
diff --git a/api/current.xml b/api/current.xml index 9422552..3ccb4e9 100644 --- a/api/current.xml +++ b/api/current.xml @@ -58662,6 +58662,21 @@ <parameter name="flags" type="int"> </parameter> </method> +<method name="setInstallerPackageName" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="targetPackage" type="java.lang.String"> +</parameter> +<parameter name="installerPackageName" type="java.lang.String"> +</parameter> +</method> <field name="COMPONENT_ENABLED_STATE_DEFAULT" type="int" transient="false" @@ -175729,6 +175744,21 @@ <parameter name="flags" type="int"> </parameter> </method> +<method name="setInstallerPackageName" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="targetPackage" type="java.lang.String"> +</parameter> +<parameter name="installerPackageName" type="java.lang.String"> +</parameter> +</method> <method name="setPackageObbPath" return="void" abstract="false" @@ -194129,53 +194159,6 @@ visibility="public" > </method> -<method name="obtain" - return="android.view.DragEvent" - abstract="false" - native="false" - synchronized="false" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="action" type="int"> -</parameter> -<parameter name="x" type="float"> -</parameter> -<parameter name="y" type="float"> -</parameter> -<parameter name="description" type="android.content.ClipDescription"> -</parameter> -<parameter name="data" type="android.content.ClipData"> -</parameter> -<parameter name="result" type="boolean"> -</parameter> -</method> -<method name="obtain" - return="android.view.DragEvent" - abstract="false" - native="false" - synchronized="false" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="source" type="android.view.DragEvent"> -</parameter> -</method> -<method name="recycle" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="true" - deprecated="not deprecated" - visibility="public" -> -</method> <method name="writeToParcel" return="void" abstract="false" diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ce9501a..abb26e3 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -914,6 +914,16 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + try { + mPM.setInstallerPackageName(targetPackage, installerPackageName); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override public void movePackage(String packageName, IPackageMoveObserver observer, int flags) { try { mPM.movePackage(packageName, observer, flags); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index a18fdac..f169cd7 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -691,8 +691,6 @@ public class DevicePolicyManager { * * <p>To disable password expiration, a value of 0 may be used for timeout. * - * <p>Timeout must be at least 1 day or IllegalArgumentException will be thrown. - * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} to be able to call this * method; if it has not, a security exception will be thrown. diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 4cff3bb..d01a68a 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -158,6 +158,8 @@ interface IPackageManager { void finishPackageInstall(int token); + void setInstallerPackageName(in String targetPackage, in String installerPackageName); + /** * Delete a package. * diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b5d1653..ac7a95a 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1918,6 +1918,24 @@ public abstract class PackageManager { String installerPackageName); /** + * Change the installer associated with a given package. There are limitations + * on how the installer package can be changed; in particular: + * <ul> + * <li> A SecurityException will be thrown if <var>installerPackageName</var> + * is not signed with the same certificate as the calling application. + * <li> A SecurityException will be thrown if <var>targetPackage</var> already + * has an installer package, and that installer package is not signed with + * the same certificate as the calling application. + * </ul> + * + * @param targetPackage The installed package whose installer will be changed. + * @param installerPackageName The package name of the new installer. May be + * null to clear the association. + */ + public abstract void setInstallerPackageName(String targetPackage, + String installerPackageName); + + /** * Attempts to delete a package. Since this may take a little while, the result will * be posted back to the given observer. A deletion will fail if the calling context * lacks the {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java index bbac14c..07e87d6 100644 --- a/core/java/android/view/DragEvent.java +++ b/core/java/android/view/DragEvent.java @@ -41,16 +41,90 @@ public class DragEvent implements Parcelable { private static DragEvent gRecyclerTop = null; /** - * action constants for DragEvent dispatch + * Action constant returned by {@link #getAction()}. Delivery of a DragEvent whose + * action is ACTION_DRAG_STARTED means that a drag operation has been initiated. The + * view receiving this DragEvent should inspect the metadata of the dragged content, + * available via {@link #getClipDescription()}, and return {@code true} from + * {@link View#onDragEvent(DragEvent)} if the view is prepared to accept a drop of + * that clip data. If the view chooses to present a visual indication that it is + * a valid target of the ongoing drag, then it should draw that indication in response + * to this event. + * <p> + * A view will only receive ACTION_DRAG_ENTERED, ACTION_DRAG_LOCATION, ACTION_DRAG_EXITED, + * and ACTION_DRAG_LOCATION events if it returns {@code true} in response to the + * ACTION_DRAG_STARTED event. */ public static final int ACTION_DRAG_STARTED = 1; + + /** + * Action constant returned by {@link #getAction()}. Delivery of a DragEvent whose + * action is ACTION_DRAG_LOCATION means that the drag operation is currently hovering + * over the view. The {@link #getX()} and {@link #getY()} methods supply the location + * of the drag point within the view's coordinate system. + * <p> + * A view will receive an ACTION_DRAG_ENTERED event before receiving any + * ACTION_DRAG_LOCATION events. If the drag point leaves the view, then an + * ACTION_DRAG_EXITED event is delivered to the view, after which no more + * ACTION_DRAG_LOCATION events will be sent (unless the drag re-enters the view, + * of course). + */ public static final int ACTION_DRAG_LOCATION = 2; + + /** + * Action constant returned by {@link #getAction()}. Delivery of a DragEvent whose + * action is ACTION_DROP means that the dragged content has been dropped on this view. + * The view should retrieve the content via {@link #getClipData()} and act on it + * appropriately. The {@link #getX()} and {@link #getY()} methods supply the location + * of the drop point within the view's coordinate system. + * <p> + * The view should return {@code true} from its {@link View#onDragEvent(DragEvent)} + * method in response to this event if it accepted the content, and {@code false} + * if it ignored the drop. + */ public static final int ACTION_DROP = 3; + + /** + * Action constant returned by {@link #getAction()}. Delivery of a DragEvent whose + * action is ACTION_DRAG_ENDED means that the drag operation has concluded. A view + * that is drawing a visual indication of drag acceptance should return to its usual + * drawing state in response to this event. + * <p> + * All views that received an ACTION_DRAG_STARTED event will receive the + * ACTION_DRAG_ENDED event even if they are not currently visible when the drag + * ends. + */ public static final int ACTION_DRAG_ENDED = 4; + + /** + * Action constant returned by {@link #getAction()}. Delivery of a DragEvent whose + * action is ACTION_DRAG_ENTERED means that the drag point has entered the view's + * bounds. If the view changed its visual state in response to the ACTION_DRAG_ENTERED + * event, it should return to its normal drag-in-progress visual state in response to + * this event. + * <p> + * A view will receive an ACTION_DRAG_ENTERED event before receiving any + * ACTION_DRAG_LOCATION events. If the drag point leaves the view, then an + * ACTION_DRAG_EXITED event is delivered to the view, after which no more + * ACTION_DRAG_LOCATION events will be sent (unless the drag re-enters the view, + * of course). + */ public static final int ACTION_DRAG_ENTERED = 5; - public static final int ACTION_DRAG_EXITED = 6; - /* hide the constructor behind package scope */ + /** + * Action constant returned by {@link #getAction()}. Delivery of a DragEvent whose + * action is ACTION_DRAG_ENTERED means that the drag point has entered the view's + * bounds. If the view chooses to present a visual indication that it will receive + * the drop if it occurs now, then it should draw that indication in response to + * this event. + * <p> + * A view will receive an ACTION_DRAG_ENTERED event before receiving any + * ACTION_DRAG_LOCATION events. If the drag point leaves the view, then an + * ACTION_DRAG_EXITED event is delivered to the view, after which no more + * ACTION_DRAG_LOCATION events will be sent (unless the drag re-enters the view, + * of course). + */ +public static final int ACTION_DRAG_EXITED = 6; + private DragEvent() { } @@ -68,6 +142,7 @@ public class DragEvent implements Parcelable { return DragEvent.obtain(0, 0f, 0f, null, null, false); } + /** @hide */ public static DragEvent obtain(int action, float x, float y, ClipDescription description, ClipData data, boolean result) { final DragEvent ev; @@ -90,31 +165,64 @@ public class DragEvent implements Parcelable { return ev; } + /** @hide */ public static DragEvent obtain(DragEvent source) { return obtain(source.mAction, source.mX, source.mY, source.mClipDescription, source.mClipData, source.mDragResult); } + /** + * Inspect the action value of this event. + * @return One of {@link #ACTION_DRAG_STARTED}, {@link #ACTION_DRAG_ENDED}, + * {@link #ACTION_DROP}, {@link #ACTION_DRAG_ENTERED}, {@link #ACTION_DRAG_EXITED}, + * or {@link #ACTION_DRAG_LOCATION}. + */ public int getAction() { return mAction; } + /** + * For ACTION_DRAG_LOCATION and ACTION_DROP events, returns the x coordinate of the + * drag point. + * @return The current drag point's x coordinate, when relevant. + */ public float getX() { return mX; } + /** + * For ACTION_DRAG_LOCATION and ACTION_DROP events, returns the y coordinate of the + * drag point. + * @return The current drag point's y coordinate, when relevant. + */ public float getY() { return mY; } + /** + * Provides the data payload of the drag operation. This payload is only available + * for events whose action value is ACTION_DROP. + * @return The ClipData containing the data being dropped on the view. + */ public ClipData getClipData() { return mClipData; } + /** + * Provides a description of the drag operation's data payload. This payload is + * available for all DragEvents other than ACTION_DROP. + * @return A ClipDescription describing the contents of the data being dragged. + */ public ClipDescription getClipDescription() { return mClipDescription; } + /** + * Provides an indication of whether the drag operation concluded successfully. + * This method is only available on ACTION_DRAG_ENDED events. + * @return {@code true} if the drag operation ended with an accepted drop; {@code false} + * otherwise. + */ public boolean getResult() { return mDragResult; } @@ -122,6 +230,8 @@ public class DragEvent implements Parcelable { /** * Recycle the DragEvent, to be re-used by a later caller. After calling * this function you must never touch the event again. + * + * @hide */ public final void recycle() { // Ensure recycle is only called once! diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 383e977..5bf2ad4 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2187,14 +2187,6 @@ public class WebView extends AbsoluteLayout private View mTitleBar; /** - * Since we draw the title bar ourselves, we removed the shadow from the - * browser's activity. We do want a shadow at the bottom of the title bar, - * or at the top of the screen if the title bar is not visible. This - * drawable serves that purpose. - */ - private Drawable mTitleShadow; - - /** * Add or remove a title bar to be embedded into the WebView, and scroll * along with it vertically, while remaining in view horizontally. Pass * null to remove the title bar from the WebView, and return to drawing @@ -2220,10 +2212,6 @@ public class WebView extends AbsoluteLayout addView(v, new AbsoluteLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0)); - if (mTitleShadow == null) { - mTitleShadow = (Drawable) mContext.getResources().getDrawable( - com.android.internal.R.drawable.title_bar_shadow); - } } mTitleBar = v; } @@ -3470,15 +3458,6 @@ public class WebView extends AbsoluteLayout drawContent(canvas); canvas.restoreToCount(saveCount); - // Now draw the shadow. - int titleH = getVisibleTitleHeight(); - if (mTitleBar != null && titleH == 0) { - int height = (int) (5f * getContext().getResources() - .getDisplayMetrics().density); - mTitleShadow.setBounds(mScrollX, mScrollY, mScrollX + getWidth(), - mScrollY + height); - mTitleShadow.draw(canvas); - } if (AUTO_REDRAW_HACK && mAutoRedraw) { invalidate(); } diff --git a/core/res/res/drawable-hdpi/title_bar_shadow.9.png b/core/res/res/drawable-hdpi/title_bar_shadow.9.png Binary files differdeleted file mode 100644 index e6dab63..0000000 --- a/core/res/res/drawable-hdpi/title_bar_shadow.9.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/title_bar_shadow.9.png b/core/res/res/drawable-ldpi/title_bar_shadow.9.png Binary files differdeleted file mode 100644 index fc45ee8..0000000 --- a/core/res/res/drawable-ldpi/title_bar_shadow.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/title_bar_shadow.9.png b/core/res/res/drawable-mdpi/title_bar_shadow.9.png Binary files differdeleted file mode 100644 index dbcefee..0000000 --- a/core/res/res/drawable-mdpi/title_bar_shadow.9.png +++ /dev/null diff --git a/core/res/res/layout/grant_credentials_permission.xml b/core/res/res/layout/grant_credentials_permission.xml index 4133ea9..0ffe8de 100644 --- a/core/res/res/layout/grant_credentials_permission.xml +++ b/core/res/res/layout/grant_credentials_permission.xml @@ -47,8 +47,7 @@ android:layout_height="wrap_content" android:fillViewport="true" android:layout_weight="1" - android:gravity="top|center_horizontal" - android:foreground="@drawable/title_bar_shadow"> + android:gravity="top|center_horizontal"> <LinearLayout android:layout_width="match_parent" diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 3e546a1..04c6538 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -67,7 +67,6 @@ <item>@drawable/text_select_handle_middle</item> <item>@drawable/text_select_handle_right</item> <item>@drawable/title_bar</item> - <item>@drawable/title_bar_shadow</item> <!-- Visual lock screen --> <item>@drawable/indicator_code_lock_drag_direction_green_up</item> <item>@drawable/indicator_code_lock_drag_direction_red_up</item> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index f5dca47..9215bf2 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -130,7 +130,7 @@ <item name="windowNoTitle">false</item> <item name="windowFullscreen">false</item> <item name="windowIsFloating">false</item> - <item name="windowContentOverlay">@android:drawable/title_bar_shadow</item> + <item name="windowContentOverlay">@null</item> <item name="windowShowWallpaper">false</item> <item name="windowTitleStyle">@android:style/WindowTitle</item> <item name="windowTitleSize">25dip</item> diff --git a/include/media/AudioEffect.h b/include/media/AudioEffect.h index c967efb..cda2be0 100644 --- a/include/media/AudioEffect.h +++ b/include/media/AudioEffect.h @@ -403,7 +403,7 @@ public: static status_t guidToString(const effect_uuid_t *guid, char *str, size_t maxLen); protected: - volatile int32_t mEnabled; // enable state + bool mEnabled; // enable state int32_t mSessionId; // audio session ID int32_t mPriority; // priority for effect control status_t mStatus; // effect status @@ -412,6 +412,7 @@ protected: void* mUserData; // client context for callback function effect_descriptor_t mDescriptor; // effect descriptor int32_t mId; // system wide unique effect engine instance ID + Mutex mLock; // Mutex for mEnabled access private: diff --git a/include/media/AudioRecord.h b/include/media/AudioRecord.h index 38e3d44..5f7cd90 100644 --- a/include/media/AudioRecord.h +++ b/include/media/AudioRecord.h @@ -356,7 +356,7 @@ private: sp<IAudioRecord> mAudioRecord; sp<IMemory> mCblkMemory; sp<ClientRecordThread> mClientRecordThread; - Mutex mRecordThreadLock; + Mutex mLock; uint32_t mFrameCount; diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h index 4475d4a..813a905 100644 --- a/include/media/AudioTrack.h +++ b/include/media/AudioTrack.h @@ -480,6 +480,7 @@ private: uint32_t mFlags; int mSessionId; int mAuxEffectId; + Mutex mLock; }; diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h index 42037af..1b0fd38 100644 --- a/media/jni/soundpool/SoundPool.h +++ b/media/jni/soundpool/SoundPool.h @@ -22,7 +22,6 @@ #include <utils/Vector.h> #include <utils/KeyedVector.h> #include <media/AudioTrack.h> -#include <cutils/atomic.h> namespace android { diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp index 88b8c86..aadeba5 100644 --- a/media/libmedia/AudioEffect.cpp +++ b/media/libmedia/AudioEffect.cpp @@ -27,7 +27,6 @@ #include <media/AudioEffect.h> #include <utils/Log.h> -#include <cutils/atomic.h> #include <binder/IPCThreadState.h> @@ -207,18 +206,22 @@ status_t AudioEffect::setEnabled(bool enabled) return INVALID_OPERATION; } - if (enabled) { - LOGV("enable %p", this); - if (android_atomic_or(1, &mEnabled) == 0) { - return mIEffect->enable(); + status_t status = NO_ERROR; + + AutoMutex lock(mLock); + if (enabled != mEnabled) { + if (enabled) { + LOGV("enable %p", this); + status = mIEffect->enable(); + } else { + LOGV("disable %p", this); + status = mIEffect->disable(); } - } else { - LOGV("disable %p", this); - if (android_atomic_and(~1, &mEnabled) == 1) { - return mIEffect->disable(); + if (status == NO_ERROR) { + mEnabled = enabled; } } - return NO_ERROR; + return status; } status_t AudioEffect::command(uint32_t cmdCode, @@ -232,26 +235,26 @@ status_t AudioEffect::command(uint32_t cmdCode, return INVALID_OPERATION; } - if ((cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) && - (replySize == NULL || *replySize != sizeof(status_t) || replyData == NULL)) { - return BAD_VALUE; + if (cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) { + if (mEnabled == (cmdCode == EFFECT_CMD_ENABLE)) { + return NO_ERROR; + } + if (replySize == NULL || *replySize != sizeof(status_t) || replyData == NULL) { + return BAD_VALUE; + } + mLock.lock(); } status_t status = mIEffect->command(cmdCode, cmdSize, cmdData, replySize, replyData); - if (status != NO_ERROR) { - return status; - } if (cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) { - status = *(status_t *)replyData; - if (status != NO_ERROR) { - return status; + if (status == NO_ERROR) { + status = *(status_t *)replyData; } - if (cmdCode == EFFECT_CMD_ENABLE) { - android_atomic_or(1, &mEnabled); - } else { - android_atomic_and(~1, &mEnabled); + if (status == NO_ERROR) { + mEnabled = (cmdCode == EFFECT_CMD_ENABLE); } + mLock.unlock(); } return status; @@ -370,11 +373,7 @@ void AudioEffect::enableStatusChanged(bool enabled) { LOGV("enableStatusChanged %p enabled %d mCbf %p", this, enabled, mCbf); if (mStatus == ALREADY_EXISTS) { - if (enabled) { - android_atomic_or(1, &mEnabled); - } else { - android_atomic_and(~1, &mEnabled); - } + mEnabled = enabled; if (mCbf) { mCbf(EVENT_ENABLE_STATUS_CHANGED, mUserData, &enabled); } diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp index a6c515c..1d6ffa0 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -35,7 +35,6 @@ #include <binder/Parcel.h> #include <binder/IPCThreadState.h> #include <utils/Timers.h> -#include <cutils/atomic.h> #define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) #define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) @@ -282,7 +281,9 @@ status_t AudioRecord::start() t->mLock.lock(); } - if (android_atomic_or(1, &mActive) == 0) { + AutoMutex lock(mLock); + if (mActive == 0) { + mActive = 1; ret = mAudioRecord->start(); if (ret == DEAD_OBJECT) { LOGV("start() dead IAudioRecord: creating a new one"); @@ -302,8 +303,7 @@ status_t AudioRecord::start() setpriority(PRIO_PROCESS, 0, THREAD_PRIORITY_AUDIO_CLIENT); } } else { - LOGV("start() failed"); - android_atomic_and(~1, &mActive); + mActive = 0; } } @@ -322,9 +322,11 @@ status_t AudioRecord::stop() if (t != 0) { t->mLock.lock(); - } + } - if (android_atomic_and(~1, &mActive) == 1) { + AutoMutex lock(mLock); + if (mActive == 1) { + mActive = 0; mCblk->cv.signal(); mAudioRecord->stop(); // the record head position will reset to 0, so if a marker is set, we need diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index 587c8ff..c1bed59 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -35,7 +35,6 @@ #include <binder/Parcel.h> #include <binder/IPCThreadState.h> #include <utils/Timers.h> -#include <cutils/atomic.h> #define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) #define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) @@ -312,7 +311,9 @@ void AudioTrack::start() t->mLock.lock(); } - if (android_atomic_or(1, &mActive) == 0) { + AutoMutex lock(mLock); + if (mActive == 0) { + mActive = 1; mNewPosition = mCblk->server + mUpdatePeriod; mCblk->bufferTimeoutMs = MAX_STARTUP_TIMEOUT_MS; mCblk->waitTimeMs = 0; @@ -344,7 +345,7 @@ void AudioTrack::start() } if (status != NO_ERROR) { LOGV("start() failed"); - android_atomic_and(~1, &mActive); + mActive = 0; if (t != 0) { t->requestExit(); } else { @@ -367,7 +368,9 @@ void AudioTrack::stop() t->mLock.lock(); } - if (android_atomic_and(~1, &mActive) == 1) { + AutoMutex lock(mLock); + if (mActive == 1) { + mActive = 0; mCblk->cv.signal(); mAudioTrack->stop(); // Cancel loops (If we are in the middle of a loop, playback @@ -407,7 +410,6 @@ void AudioTrack::flush() mMarkerReached = false; mUpdatePeriod = 0; - if (!mActive) { mAudioTrack->flush(); // Release AudioTrack callback thread in case it was waiting for new buffers @@ -419,7 +421,9 @@ void AudioTrack::flush() void AudioTrack::pause() { LOGV("pause"); - if (android_atomic_and(~1, &mActive) == 1) { + AutoMutex lock(mLock); + if (mActive == 1) { + mActive = 0; mAudioTrack->pause(); } } diff --git a/media/tests/CameraBrowser/Android.mk b/media/tests/CameraBrowser/Android.mk index 1d81129..295b3e6 100644 --- a/media/tests/CameraBrowser/Android.mk +++ b/media/tests/CameraBrowser/Android.mk @@ -1,7 +1,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_MODULE_TAGS := tests +LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java index 34dbace..82753b2 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java @@ -43,13 +43,6 @@ public abstract class KeyguardViewBase extends FrameLayout { public KeyguardViewBase(Context context) { super(context); - - // drop shadow below status bar in keyguard too - mForegroundInPadding = false; - setForegroundGravity(Gravity.FILL_HORIZONTAL | Gravity.TOP); - setForeground( - context.getResources().getDrawable( - com.android.internal.R.drawable.title_bar_shadow)); } // used to inject callback diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 84dd022..51b5947 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -343,7 +343,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( lSessionId = *sessionId; } else { // if no audio session id is provided, create one here - lSessionId = nextUniqueId(); + lSessionId = nextUniqueId_l(); if (sessionId != NULL) { *sessionId = lSessionId; } @@ -3699,7 +3699,7 @@ sp<IAudioRecord> AudioFlinger::openRecord( if (sessionId != NULL && *sessionId != AudioSystem::SESSION_OUTPUT_MIX) { lSessionId = *sessionId; } else { - lSessionId = nextUniqueId(); + lSessionId = nextUniqueId_l(); if (sessionId != NULL) { *sessionId = lSessionId; } @@ -4300,7 +4300,7 @@ int AudioFlinger::openOutput(uint32_t *pDevices, mHardwareStatus = AUDIO_HW_IDLE; if (output != 0) { - int id = nextUniqueId(); + int id = nextUniqueId_l(); if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) || (format != AudioSystem::PCM_16_BIT) || (channels != AudioSystem::CHANNEL_OUT_STEREO)) { @@ -4348,7 +4348,7 @@ int AudioFlinger::openDuplicateOutput(int output1, int output2) return 0; } - int id = nextUniqueId(); + int id = nextUniqueId_l(); DuplicatingThread *thread = new DuplicatingThread(this, thread1, id); thread->addOutputTrack(thread2); mPlaybackThreads.add(id, thread); @@ -4473,7 +4473,7 @@ int AudioFlinger::openInput(uint32_t *pDevices, } if (input != 0) { - int id = nextUniqueId(); + int id = nextUniqueId_l(); // Start record thread thread = new RecordThread(this, input, reqSamplingRate, reqChannels, id); mRecordThreads.add(id, thread); @@ -4543,7 +4543,8 @@ status_t AudioFlinger::setStreamOutput(uint32_t stream, int output) int AudioFlinger::newAudioSessionId() { - return nextUniqueId(); + AutoMutex _l(mLock); + return nextUniqueId_l(); } // checkPlaybackThread_l() must be called with AudioFlinger::mLock held @@ -4578,9 +4579,10 @@ AudioFlinger::RecordThread *AudioFlinger::checkRecordThread_l(int input) const return thread; } -int AudioFlinger::nextUniqueId() +// nextUniqueId_l() must be called with AudioFlinger::mLock held +int AudioFlinger::nextUniqueId_l() { - return android_atomic_inc(&mNextUniqueId); + return mNextUniqueId++; } // ---------------------------------------------------------------------------- @@ -4967,7 +4969,7 @@ sp<AudioFlinger::EffectHandle> AudioFlinger::PlaybackThread::createEffect_l( LOGV("createEffect_l() got effect %p on chain %p", effect == 0 ? 0 : effect.get(), chain.get()); if (effect == 0) { - int id = mAudioFlinger->nextUniqueId(); + int id = mAudioFlinger->nextUniqueId_l(); // Check CPU and memory usage lStatus = AudioSystem::registerEffect(desc, mId, chain->strategy(), sessionId, id); if (lStatus != NO_ERROR) { diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index 5917632..f0ef867 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -785,7 +785,7 @@ private: float streamVolumeInternal(int stream) const { return mStreamTypes[stream].volume; } void audioConfigChanged_l(int event, int ioHandle, void *param2); - int nextUniqueId(); + int nextUniqueId_l(); status_t moveEffectChain_l(int session, AudioFlinger::PlaybackThread *srcThread, AudioFlinger::PlaybackThread *dstThread, diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 2b43b01..53a19f5 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -41,9 +41,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; -import android.net.ConnectivityManager; +import android.content.pm.ResolveInfo; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -55,9 +54,9 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Settings; -import android.util.Slog; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.util.Slog; import android.util.Xml; import android.view.WindowManagerPolicy; @@ -89,9 +88,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { = "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION"; private static final long MS_PER_DAY = 86400 * 1000; - private static final long MS_PER_HOUR = 3600 * 1000; - private static final long MS_PER_MINUTE = 60 * 1000; - private static final long MIN_TIMEOUT = 86400 * 1000; // minimum expiration timeout is 1 day final Context mContext; final MyPackageMonitor mMonitor; @@ -364,6 +360,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } class MyPackageMonitor extends PackageMonitor { + @Override public void onSomePackagesChanged() { synchronized (DevicePolicyManagerService.this) { boolean removed = false; @@ -410,13 +407,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { context.registerReceiver(mReceiver, filter); } - static String countdownString(long time) { - long days = time / MS_PER_DAY; - long hours = (time / MS_PER_HOUR) % 24; - long minutes = (time / MS_PER_MINUTE) % 60; - return days + "d" + hours + "h" + minutes + "m"; - } - protected void setExpirationAlarmCheckLocked(Context context) { final long expiration = getPasswordExpirationLocked(null); final long now = System.currentTimeMillis(); @@ -430,12 +420,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { alarmTime = now + MS_PER_DAY; } - AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_EXPIRE_PASSWORD, - new Intent(ACTION_EXPIRED_PASSWORD_NOTIFICATION), - PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); - am.cancel(pi); - am.set(AlarmManager.RTC, alarmTime, pi); + long token = Binder.clearCallingIdentity(); + try { + AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_EXPIRE_PASSWORD, + new Intent(ACTION_EXPIRED_PASSWORD_NOTIFICATION), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); + am.cancel(pi); + am.set(AlarmManager.RTC, alarmTime, pi); + } finally { + Binder.restoreCallingIdentity(token); + } } private IPowerManager getIPowerManager() { @@ -993,8 +988,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who == null) { throw new NullPointerException("ComponentName is null"); } - if (timeout != 0L && timeout < MIN_TIMEOUT) { - throw new IllegalArgumentException("Timeout must be > " + MIN_TIMEOUT + "ms"); + if (timeout < 0) { + throw new IllegalArgumentException("Timeout must be >= 0 ms"); } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD); diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index a0a1974..c121808 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -4578,6 +4578,80 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + final int permission = mContext.checkCallingPermission( + android.Manifest.permission.INSTALL_PACKAGES); + final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + synchronized (mPackages) { + PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage); + if (targetPackageSetting == null) { + throw new IllegalArgumentException("Unknown target package: " + targetPackage); + } + + PackageSetting installerPackageSetting; + if (installerPackageName != null) { + installerPackageSetting = mSettings.mPackages.get(installerPackageName); + if (installerPackageSetting == null) { + throw new IllegalArgumentException("Unknown installer package: " + + installerPackageName); + } + } else { + installerPackageSetting = null; + } + + Signature[] callerSignature; + Object obj = mSettings.getUserIdLP(uid); + if (obj != null) { + if (obj instanceof SharedUserSetting) { + callerSignature = ((SharedUserSetting)obj).signatures.mSignatures; + } else if (obj instanceof PackageSetting) { + callerSignature = ((PackageSetting)obj).signatures.mSignatures; + } else { + throw new SecurityException("Bad object " + obj + " for uid " + uid); + } + } else { + throw new SecurityException("Unknown calling uid " + uid); + } + + // Verify: can't set installerPackageName to a package that is + // not signed with the same cert as the caller. + if (installerPackageSetting != null) { + if (checkSignaturesLP(callerSignature, + installerPackageSetting.signatures.mSignatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as new installer package " + + installerPackageName); + } + } + + // Verify: if target already has an installer package, it must + // be signed with the same cert as the caller. + if (targetPackageSetting.installerPackageName != null) { + PackageSetting setting = mSettings.mPackages.get( + targetPackageSetting.installerPackageName); + // If the currently set package isn't valid, then it's always + // okay to change it. + if (setting != null) { + if (checkSignaturesLP(callerSignature, + setting.signatures.mSignatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as old installer package " + + targetPackageSetting.installerPackageName); + } + } + } + + // Okay! + targetPackageSetting.installerPackageName = installerPackageName; + scheduleWriteSettingsLocked(); + } + } + public void setPackageObbPath(String packageName, String path) { if (DEBUG_OBB) Log.v(TAG, "Setting .obb path for " + packageName + " to: " + path); diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index f0cbaa0..615870b 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -341,6 +341,12 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + @Override + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + throw new UnsupportedOperationException(); + } + /** * @hide - to match hiding in superclass */ diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index a5f3456..cea07af 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -18,6 +18,7 @@ package android.graphics; import com.android.layoutlib.api.ILayoutLog; import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.Stack; import android.graphics.Paint_Delegate.FontInfo; import android.text.TextUtils; @@ -32,7 +33,6 @@ import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.util.List; -import java.util.Stack; /** diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index ac7fada..2ddabdf 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -22,6 +22,7 @@ import com.android.layoutlib.api.IResourceValue; import com.android.layoutlib.api.IStyleResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.impl.Stack; import com.android.layoutlib.bridge.impl.TempResourceValue; import android.app.Activity; @@ -65,7 +66,6 @@ import java.io.InputStream; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; -import java.util.Stack; import java.util.TreeMap; import java.util.Map.Entry; @@ -191,14 +191,33 @@ public final class BridgeContext extends Activity { return mDefaultPropMaps.get(key); } + /** + * Adds a parser to the stack. + * @param parser the parser to add. + */ public void pushParser(BridgeXmlBlockParser parser) { mParserStack.push(parser); } + /** + * Removes the parser at the top of the stack + */ public void popParser() { mParserStack.pop(); } + /** + * Returns the current parser at the top the of the stack. + * @return a parser or null. + */ + public BridgeXmlBlockParser getCurrentParser() { + return mParserStack.peek(); + } + + /** + * Returns the previous parser. + * @return a parser or null if there isn't any previous parser + */ public BridgeXmlBlockParser getPreviousParser() { if (mParserStack.size() < 2) { return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java index bfbb01a8..1011173 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java @@ -220,24 +220,37 @@ public final class BridgeResources extends Resources { IResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - File xml = new File(value.getValue()); - if (xml.isFile()) { - // we need to create a pull parser around the layout XML file, and then - // give that to our XmlBlockParser - try { - KXmlParser parser = new KXmlParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(xml)); + XmlPullParser parser = null; - return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); - } catch (XmlPullParserException e) { - mContext.getLogger().error(e); + try { + // check if the current parser can provide us with a custom parser. + BridgeXmlBlockParser currentParser = mContext.getCurrentParser(); + if (currentParser != null) { + parser = currentParser.getParser(value.getName()); + } + + // create a new one manually if needed. + if (parser == null) { + File xml = new File(value.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileReader(xml)); + } + } - // we'll return null below. - } catch (FileNotFoundException e) { - // this shouldn't happen since we check above. + if (parser != null) { + return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); } + } catch (XmlPullParserException e) { + mContext.getLogger().error(e); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. } + } // id was not found or not resolved. Throw a NotFoundException. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java index 073a019..c3d0b14 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java @@ -56,13 +56,23 @@ public class BridgeXmlBlockParser implements XmlResourceParser { mPlatformFile = platformFile; mAttrib = new BridgeXmlPullAttributes(parser, context, mPlatformFile); - mContext.pushParser(this); + if (mContext != null) { + mContext.pushParser(this); + } } public boolean isPlatformFile() { return mPlatformFile; } + public IXmlPullParser getParser(String layoutName) { + if (mParser instanceof IXmlPullParser) { + return ((IXmlPullParser)mParser).getParser(layoutName); + } + + return null; + } + public Object getViewKey() { if (mParser instanceof IXmlPullParser) { return ((IXmlPullParser)mParser).getViewKey(); @@ -238,7 +248,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { } int ev = mParser.next(); - if (ev == END_TAG && mParser.getDepth() == 1) { + if (ev == END_TAG && mParser.getDepth() == 1 && mContext != null) { // done with parser remove it from the context stack. mContext.popParser(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java index b0316a3..2e3f9a8 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java @@ -25,6 +25,7 @@ import com.android.layoutlib.api.SceneParams; import com.android.layoutlib.api.SceneResult; import com.android.layoutlib.api.ViewInfo; import com.android.layoutlib.api.IDensityBasedResourceValue.Density; +import com.android.layoutlib.api.SceneParams.RenderingMode; import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeInflater; @@ -259,22 +260,32 @@ public class LayoutSceneImpl { int renderScreenWidth = mParams.getScreenWidth(); int renderScreenHeight = mParams.getScreenHeight(); - if (mParams.getRenderFullSize()) { + RenderingMode renderingMode = mParams.getRenderingMode(); + + if (renderingMode != RenderingMode.NORMAL) { // measure the full size needed by the layout. w_spec = MeasureSpec.makeMeasureSpec(renderScreenWidth, - MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size + renderingMode.isHorizExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); h_spec = MeasureSpec.makeMeasureSpec(renderScreenHeight - mScreenOffset, - MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size + renderingMode.isVertExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); mViewRoot.measure(w_spec, h_spec); - int neededWidth = mViewRoot.getChildAt(0).getMeasuredWidth(); - if (neededWidth > renderScreenWidth) { - renderScreenWidth = neededWidth; + if (renderingMode.isHorizExpand()) { + int neededWidth = mViewRoot.getChildAt(0).getMeasuredWidth(); + if (neededWidth > renderScreenWidth) { + renderScreenWidth = neededWidth; + } } - int neededHeight = mViewRoot.getChildAt(0).getMeasuredHeight(); - if (neededHeight > renderScreenHeight - mScreenOffset) { - renderScreenHeight = neededHeight + mScreenOffset; + if (renderingMode.isVertExpand()) { + int neededHeight = mViewRoot.getChildAt(0).getMeasuredHeight(); + if (neededHeight > renderScreenHeight - mScreenOffset) { + renderScreenHeight = neededHeight + mScreenOffset; + } } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java new file mode 100644 index 0000000..9bd0015 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.impl; + +import java.util.ArrayList; + +/** + * Custom Stack implementation on top of an {@link ArrayList} instead of + * using {@link java.util.Stack} which is on top of a vector. + * + * @param <T> + */ +public class Stack<T> extends ArrayList<T> { + + private static final long serialVersionUID = 1L; + + public Stack() { + super(); + } + + public Stack(int size) { + super(size); + } + + /** + * Pushes the given object to the stack + * @param object the object to push + */ + public void push(T object) { + add(object); + } + + /** + * Remove the object at the top of the stack and returns it. + * @return the removed object or null if the stack was empty. + */ + public T pop() { + if (size() > 0) { + return remove(size() - 1); + } + + return null; + } + + /** + * Returns the object at the top of the stack. + * @return the object at the top or null if the stack is empty. + */ + public T peek() { + if (size() > 0) { + return get(size() - 1); + } + + return null; + } +} |
