summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorChris Wren <cwren@android.com>2012-09-26 10:25:53 -0400
committerChris Wren <cwren@android.com>2012-09-26 11:52:00 -0400
commitb8235acb0fdc33c50e864ec801b93b9750d7600c (patch)
tree21222f984fc0a5653732609d35fb731beb76ef4c /src
parentcbbd63fc12c8e6bf4f329d82b4d101bf5e00643f (diff)
downloadandroid_packages_screensavers_PhotoTable-b8235acb0fdc33c50e864ec801b93b9750d7600c.tar.gz
android_packages_screensavers_PhotoTable-b8235acb0fdc33c50e864ec801b93b9750d7600c.tar.bz2
android_packages_screensavers_PhotoTable-b8235acb0fdc33c50e864ec801b93b9750d7600c.zip
refine handling of exceptional cases: no settings, network failure, load error.
Bug: 7194196 Bug: 7152553 Change-Id: I4335e46fe3a61a09ce3b14a02bb199a84126e53f
Diffstat (limited to 'src')
-rw-r--r--src/com/android/dreams/phototable/AlbumSettings.java7
-rw-r--r--src/com/android/dreams/phototable/BummerView.java106
-rw-r--r--src/com/android/dreams/phototable/FlipperDream.java10
-rw-r--r--src/com/android/dreams/phototable/FlipperDreamSettings.java1
-rw-r--r--src/com/android/dreams/phototable/PhotoSource.java168
-rw-r--r--src/com/android/dreams/phototable/PhotoSourcePlexor.java12
-rw-r--r--src/com/android/dreams/phototable/PhotoTableDream.java24
-rw-r--r--src/com/android/dreams/phototable/PhotoTableDreamSettings.java1
-rw-r--r--src/com/android/dreams/phototable/PicasaSource.java1
-rw-r--r--src/com/android/dreams/phototable/StockSource.java38
10 files changed, 247 insertions, 121 deletions
diff --git a/src/com/android/dreams/phototable/AlbumSettings.java b/src/com/android/dreams/phototable/AlbumSettings.java
index 6a26a3c..502cd3f 100644
--- a/src/com/android/dreams/phototable/AlbumSettings.java
+++ b/src/com/android/dreams/phototable/AlbumSettings.java
@@ -24,13 +24,12 @@ import java.util.Set;
* Common utilities for album settings.
*/
public class AlbumSettings {
- public static final String ALBUM_SET = "Enabled Album Set";
+ public static final String ALBUM_SET = "Enabled Album Set V2";
public static Set<String> getEnabledAlbums(SharedPreferences settings) {
Set<String> enabled = settings.getStringSet(ALBUM_SET, null);
if (enabled == null) {
enabled = new HashSet<String>();
- enabled.add(StockSource.ALBUM_ID);
setEnabledAlbums(settings, enabled);
}
return enabled;
@@ -41,4 +40,8 @@ public class AlbumSettings {
editor.putStringSet(ALBUM_SET, value);
editor.apply();
}
+
+ public static boolean isConfigured(SharedPreferences settings) {
+ return getEnabledAlbums(settings).size() != 0;
+ }
}
diff --git a/src/com/android/dreams/phototable/BummerView.java b/src/com/android/dreams/phototable/BummerView.java
new file mode 100644
index 0000000..ff0a11f
--- /dev/null
+++ b/src/com/android/dreams/phototable/BummerView.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dreams.phototable;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+public class BummerView extends TextView {
+ public static final int START = 1;
+ public static final int MOVE = 2;
+
+ private int mDelay = 10000; // ms
+ private int mAnimTime = 2000; // ms
+ private boolean mAnimate = false;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message m) {
+ boolean animate = false;
+ switch (m.what) {
+ case MOVE:
+ animate = mAnimate;
+ // fall through
+ case START:
+ final View parent = (View) BummerView.this.getParent();
+ if (parent == null)
+ return;
+
+ final float framew = parent.getMeasuredWidth();
+ final float frameh = parent.getMeasuredHeight();
+ final float textw = getMeasuredWidth();
+ final float texth = getMeasuredHeight();
+
+ final float newx = (float) (Math.random() * (framew - textw));
+ final float newy = (float) (Math.random() * (frameh - texth));
+ if (animate) {
+ animate().x(newx)
+ .y(newy)
+ .setDuration(mAnimTime)
+ .start();
+ } else {
+ setX(newx);
+ setY(newy);
+ }
+ setVisibility(View.VISIBLE);
+
+ removeMessages(MOVE);
+ sendEmptyMessageDelayed(MOVE, mDelay);
+ break;
+ }
+ }
+ };
+
+ public BummerView(Context context) {
+ super(context);
+ }
+
+ public BummerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BummerView(Context context, AttributeSet attrs, int flags) {
+ super(context, attrs, flags);
+ }
+
+ public void setAnimationParams(boolean animate, int delay, int animTime) {
+ mAnimate = animate;
+ mDelay = delay;
+ mAnimTime = animTime;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ final View parent = (View) this.getParent();
+ parent.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (v == parent && right != oldRight) {
+ mHandler.removeMessages(MOVE);
+ mHandler.sendEmptyMessage(START);
+ }
+ }
+ });
+
+ mHandler.sendEmptyMessage(START);
+ }
+
+}
diff --git a/src/com/android/dreams/phototable/FlipperDream.java b/src/com/android/dreams/phototable/FlipperDream.java
index 32a43d1..f64b7cb 100644
--- a/src/com/android/dreams/phototable/FlipperDream.java
+++ b/src/com/android/dreams/phototable/FlipperDream.java
@@ -15,6 +15,7 @@
*/
package com.android.dreams.phototable;
+import android.content.SharedPreferences;
import android.service.dreams.Dream;
/**
@@ -32,7 +33,14 @@ public class FlipperDream extends Dream {
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
+
+ SharedPreferences settings = getSharedPreferences(FlipperDreamSettings.PREFS_NAME, 0);
+ if (AlbumSettings.isConfigured(settings)) {
+ setContentView(R.layout.carousel);
+ } else {
+ setContentView(R.layout.bummer);
+ }
+
setFullscreen(true);
- setContentView(R.layout.carousel);
}
}
diff --git a/src/com/android/dreams/phototable/FlipperDreamSettings.java b/src/com/android/dreams/phototable/FlipperDreamSettings.java
index 720599f..cac415c 100644
--- a/src/com/android/dreams/phototable/FlipperDreamSettings.java
+++ b/src/com/android/dreams/phototable/FlipperDreamSettings.java
@@ -48,5 +48,6 @@ public class FlipperDreamSettings extends ListActivity {
R.layout.album,
new LinkedList<PhotoSource.AlbumData>(mPhotoSource.findAlbums()));
setListAdapter(mAdapter);
+ setContentView(R.layout.settingslist);
}
}
diff --git a/src/com/android/dreams/phototable/PhotoSource.java b/src/com/android/dreams/phototable/PhotoSource.java
index c2b423b..44e00d9 100644
--- a/src/com/android/dreams/phototable/PhotoSource.java
+++ b/src/com/android/dreams/phototable/PhotoSource.java
@@ -41,7 +41,7 @@ import java.util.Random;
*/
public abstract class PhotoSource {
private static final String TAG = "PhotoTable.PhotoSource";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
// This should be large enough for BitmapFactory to decode the header so
// that we can mark and reset the input stream to avoid duplicate network i/o
@@ -74,6 +74,7 @@ public abstract class PhotoSource {
private final LinkedList<ImageData> mImageQueue;
private final int mMaxQueueSize;
private final float mMaxCropRatio;
+ private final PhotoSource mFallbackSource;
protected final Context mContext;
protected final Resources mResources;
@@ -84,6 +85,10 @@ public abstract class PhotoSource {
protected String mSourceName;
public PhotoSource(Context context, SharedPreferences settings) {
+ this(context, settings, new StockSource(context, settings));
+ }
+
+ public PhotoSource(Context context, SharedPreferences settings, PhotoSource fallbackSource) {
mSourceName = TAG;
mContext = context;
mSettings = settings;
@@ -93,6 +98,7 @@ public abstract class PhotoSource {
mMaxQueueSize = mResources.getInteger(R.integer.image_queue_size);
mMaxCropRatio = mResources.getInteger(R.integer.max_crop_ratio) / 1000000f;
mRNG = new Random();
+ mFallbackSource = fallbackSource;
}
protected void fillQueue() {
@@ -111,87 +117,97 @@ public abstract class PhotoSource {
}
if (!mImageQueue.isEmpty()) {
- ImageData data = mImageQueue.poll();
- InputStream is = null;
- try {
- is = data.getStream();
- BufferedInputStream bis = new BufferedInputStream(is);
- bis.mark(BUFFER_SIZE);
-
- options.inJustDecodeBounds = true;
- options.inSampleSize = 1;
- image = BitmapFactory.decodeStream(new BufferedInputStream(bis), null, options);
- int rawLongSide = Math.max(options.outWidth, options.outHeight);
- int rawShortSide = Math.min(options.outWidth, options.outHeight);
- log(TAG, "I see bounds of " + rawLongSide + ", " + rawShortSide);
-
- if (rawLongSide != -1 && rawShortSide != -1) {
- float insideRatio = Math.max((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
- float outsideRatio = Math.max((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
- float ratio = (outsideRatio / insideRatio < mMaxCropRatio ?
- outsideRatio : insideRatio);
-
- while (ratio < 0.5) {
- options.inSampleSize *= 2;
- ratio *= 2;
- }
+ image = load(mImageQueue.poll(), options, longSide, shortSide);
+ }
- log(TAG, "decoding with inSampleSize " + options.inSampleSize);
- bis.reset();
- options.inJustDecodeBounds = false;
- image = BitmapFactory.decodeStream(bis, null, options);
- rawLongSide = Math.max(options.outWidth, options.outHeight);
- rawShortSide = Math.max(options.outWidth, options.outHeight);
- ratio = Math.max((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
-
- if (ratio < 1.0f) {
- log(TAG, "still too big, scaling down by " + ratio);
- options.outWidth = (int) (ratio * options.outWidth);
- options.outHeight = (int) (ratio * options.outHeight);
-
- image = Bitmap.createScaledBitmap(image,
- options.outWidth, options.outHeight,
- true);
- }
+ if (image == null && mFallbackSource != null) {
+ image = load((ImageData) mFallbackSource.findImages(1).toArray()[0],
+ options, longSide, shortSide);
+ }
- if (data.orientation != 0) {
- log(TAG, "rotated by " + data.orientation + ": fixing");
- if (data.orientation == 90 || data.orientation == 270) {
- int tmp = options.outWidth;
- options.outWidth = options.outHeight;
- options.outHeight = tmp;
- }
- Matrix matrix = new Matrix();
- matrix.setRotate(data.orientation,
- (float) image.getWidth() / 2,
- (float) image.getHeight() / 2);
- image = Bitmap.createBitmap(image, 0, 0,
- options.outHeight, options.outWidth,
- matrix, true);
- }
+ return image;
+ }
- log(TAG, "returning bitmap " + image.getWidth() + ", " + image.getHeight());
- } else {
- log(TAG, "decoding failed with no error: " + options.mCancel);
+ public Bitmap load(ImageData data, BitmapFactory.Options options, int longSide, int shortSide) {
+ log(TAG, "decoding photo resource to " + longSide + ", " + shortSide);
+ InputStream is = data.getStream();
+
+ Bitmap image = null;
+ try {
+ BufferedInputStream bis = new BufferedInputStream(is);
+ bis.mark(BUFFER_SIZE);
+
+ options.inJustDecodeBounds = true;
+ options.inSampleSize = 1;
+ image = BitmapFactory.decodeStream(new BufferedInputStream(bis), null, options);
+ int rawLongSide = Math.max(options.outWidth, options.outHeight);
+ int rawShortSide = Math.min(options.outWidth, options.outHeight);
+ log(TAG, "I see bounds of " + rawLongSide + ", " + rawShortSide);
+
+ if (rawLongSide != -1 && rawShortSide != -1) {
+ float insideRatio = Math.max((float) longSide / (float) rawLongSide,
+ (float) shortSide / (float) rawShortSide);
+ float outsideRatio = Math.max((float) longSide / (float) rawLongSide,
+ (float) shortSide / (float) rawShortSide);
+ float ratio = (outsideRatio / insideRatio < mMaxCropRatio ?
+ outsideRatio : insideRatio);
+
+ while (ratio < 0.5) {
+ options.inSampleSize *= 2;
+ ratio *= 2;
}
- } catch (FileNotFoundException fnf) {
- log(TAG, "file not found: " + fnf);
- } catch (IOException ioe) {
- log(TAG, "i/o exception: " + ioe);
- } finally {
- try {
- if (is != null) {
- is.close();
+
+ log(TAG, "decoding with inSampleSize " + options.inSampleSize);
+ bis.reset();
+ options.inJustDecodeBounds = false;
+ image = BitmapFactory.decodeStream(bis, null, options);
+ rawLongSide = Math.max(options.outWidth, options.outHeight);
+ rawShortSide = Math.max(options.outWidth, options.outHeight);
+ ratio = Math.max((float) longSide / (float) rawLongSide,
+ (float) shortSide / (float) rawShortSide);
+
+ if (Math.abs(ratio - 1.0f) > 0.001) {
+ log(TAG, "still too big, scaling down by " + ratio);
+ options.outWidth = (int) (ratio * options.outWidth);
+ options.outHeight = (int) (ratio * options.outHeight);
+
+ image = Bitmap.createScaledBitmap(image,
+ options.outWidth, options.outHeight,
+ true);
+ }
+
+ if (data.orientation != 0) {
+ log(TAG, "rotated by " + data.orientation + ": fixing");
+ if (data.orientation == 90 || data.orientation == 270) {
+ int tmp = options.outWidth;
+ options.outWidth = options.outHeight;
+ options.outHeight = tmp;
}
- } catch (Throwable t) {
- log(TAG, "close fail: " + t.toString());
+ Matrix matrix = new Matrix();
+ matrix.setRotate(data.orientation,
+ (float) image.getWidth() / 2,
+ (float) image.getHeight() / 2);
+ image = Bitmap.createBitmap(image, 0, 0,
+ options.outHeight, options.outWidth,
+ matrix, true);
+ }
+
+ log(TAG, "returning bitmap " + image.getWidth() + ", " + image.getHeight());
+ } else {
+ log(TAG, "decoding failed with no error: " + options.mCancel);
+ }
+ } catch (FileNotFoundException fnf) {
+ log(TAG, "file not found: " + fnf);
+ } catch (IOException ioe) {
+ log(TAG, "i/o exception: " + ioe);
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
}
+ } catch (Throwable t) {
+ log(TAG, "close fail: " + t.toString());
}
- } else {
- log(TAG, mSourceName + " has no images.");
}
return image;
@@ -201,7 +217,7 @@ public abstract class PhotoSource {
mRNG.setSeed(seed);
}
- protected void log(String tag, String message) {
+ protected static void log(String tag, String message) {
if (DEBUG) {
Log.i(tag, message);
}
diff --git a/src/com/android/dreams/phototable/PhotoSourcePlexor.java b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
index 7cf207e..cf19d80 100644
--- a/src/com/android/dreams/phototable/PhotoSourcePlexor.java
+++ b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
@@ -34,7 +34,6 @@ public class PhotoSourcePlexor extends PhotoSource {
private final PhotoSource mPicasaSource;
private final PhotoSource mLocalSource;
- private final PhotoSource mStockSource;
private SharedPreferences mSettings;
public PhotoSourcePlexor(Context context, SharedPreferences settings) {
@@ -42,7 +41,6 @@ public class PhotoSourcePlexor extends PhotoSource {
mSourceName = TAG;
mPicasaSource = new PicasaSource(context, settings);
mLocalSource = new LocalSource(context, settings);
- mStockSource = new StockSource(context, settings);
}
@Override
@@ -56,11 +54,6 @@ public class PhotoSourcePlexor extends PhotoSource {
foundAlbums.addAll(mLocalSource.findAlbums());
log(TAG, "found " + foundAlbums.size() + " user albums");
- if (foundAlbums.isEmpty()) {
- foundAlbums.addAll(mStockSource.findAlbums());
- }
- log(TAG, "found " + foundAlbums.size() + " albums");
-
return foundAlbums;
}
@@ -75,11 +68,6 @@ public class PhotoSourcePlexor extends PhotoSource {
foundImages.addAll(mLocalSource.findImages(howMany));
log(TAG, "found " + foundImages.size() + " user images");
- if (foundImages.isEmpty()) {
- foundImages.addAll(mStockSource.findImages(howMany));
- }
- log(TAG, "found " + foundImages.size() + " images");
-
return foundImages;
}
diff --git a/src/com/android/dreams/phototable/PhotoTableDream.java b/src/com/android/dreams/phototable/PhotoTableDream.java
index cebd3f8..21ae694 100644
--- a/src/com/android/dreams/phototable/PhotoTableDream.java
+++ b/src/com/android/dreams/phototable/PhotoTableDream.java
@@ -16,10 +16,14 @@
package com.android.dreams.phototable;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
import android.service.dreams.Dream;
import android.view.LayoutInflater;
import android.view.ViewGroup;
+import java.util.Set;
+
/**
* Example interactive screen saver: flick photos onto a table.
*/
@@ -38,10 +42,22 @@ public class PhotoTableDream extends Dream {
super.onAttachedToWindow();
LayoutInflater inflater =
(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- ViewGroup view = (ViewGroup) inflater.inflate(R.layout.table, null);
- PhotoTable table = (PhotoTable) view.findViewById(R.id.table);
- table.setDream(this);
- setContentView(view);
+ SharedPreferences settings = getSharedPreferences(PhotoTableDreamSettings.PREFS_NAME, 0);
+ Set<String> enabledAlbums = AlbumSettings.getEnabledAlbums(settings);
+ if (AlbumSettings.isConfigured(settings)) {
+ ViewGroup view = (ViewGroup) inflater.inflate(R.layout.table, null);
+ PhotoTable table = (PhotoTable) view.findViewById(R.id.table);
+ table.setDream(this);
+ setContentView(view);
+ } else {
+ Resources resources = getResources();
+ ViewGroup view = (ViewGroup) inflater.inflate(R.layout.bummer, null);
+ BummerView bummer = (BummerView) view.findViewById(R.id.bummer);
+ bummer.setAnimationParams(true,
+ resources.getInteger(R.integer.table_drop_period),
+ resources.getInteger(R.integer.fast_drop));
+ setContentView(view);
+ }
setFullscreen(true);
}
}
diff --git a/src/com/android/dreams/phototable/PhotoTableDreamSettings.java b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
index 7271f3f..4340eba 100644
--- a/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
+++ b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
@@ -48,5 +48,6 @@ public class PhotoTableDreamSettings extends ListActivity {
R.layout.album,
new LinkedList<PhotoSource.AlbumData>(mPhotoSource.findAlbums()));
setListAdapter(mAdapter);
+ setContentView(R.layout.settingslist);
}
}
diff --git a/src/com/android/dreams/phototable/PicasaSource.java b/src/com/android/dreams/phototable/PicasaSource.java
index cd5ddcd..92adfa6 100644
--- a/src/com/android/dreams/phototable/PicasaSource.java
+++ b/src/com/android/dreams/phototable/PicasaSource.java
@@ -75,7 +75,6 @@ public class PicasaSource extends PhotoSource {
mPostsAlbumName = mResources.getString(R.string.posts_album_name, "Posts");
mUploadsAlbumName = mResources.getString(R.string.uploads_album_name, "Instant Uploads");
mUnknownAlbumName = mResources.getString(R.string.unknown_album_name, "Unknown");
- log(TAG, "mSettings: " + mSettings);
fillQueue();
}
diff --git a/src/com/android/dreams/phototable/StockSource.java b/src/com/android/dreams/phototable/StockSource.java
index fff7e61..1e00780 100644
--- a/src/com/android/dreams/phototable/StockSource.java
+++ b/src/com/android/dreams/phototable/StockSource.java
@@ -21,42 +21,30 @@ import android.util.Log;
import java.io.InputStream;
import java.util.Collection;
-import java.util.LinkedList;
+import java.util.ArrayList;
/**
* Picks a random image from the local store.
*/
-public class StockSource extends PhotoSource {
+public class
+
+StockSource extends PhotoSource {
public static final String ALBUM_ID = "com.android.dreams.phototable.StockSource";
private static final String TAG = "PhotoTable.StockSource";
- private static final int[] PHOTOS = {R.drawable.photo_044_002,
- R.drawable.photo_039_002,
- R.drawable.photo_059_003,
- R.drawable.photo_070_004,
- R.drawable.photo_072_001,
- R.drawable.photo_077_002,
- R.drawable.photo_098_002,
- R.drawable.photo_119_003,
- R.drawable.photo_119_004,
- R.drawable.photo_126_001,
- R.drawable.photo_147_002,
- R.drawable.photo_175_004
- };
+ private static final int[] PHOTOS = { R.drawable.blank_photo };
- private final LinkedList<ImageData> mImageList;
- private final LinkedList<AlbumData> mAlbumList;
+ private final ArrayList<ImageData> mImageList;
+ private final ArrayList<AlbumData> mAlbumList;
private final String mStockPhotoName;
- private final String mStockThumbnail;
private int mNextPosition;
public StockSource(Context context, SharedPreferences settings) {
- super(context, settings);
+ super(context, settings, null);
mSourceName = TAG;
mStockPhotoName = mResources.getString(R.string.stock_photo_album_name, "Default Photos");
- mStockThumbnail = mResources.getString(R.string.stock_photo_thumbnail_url);
- mImageList = new LinkedList<ImageData>();
- mAlbumList = new LinkedList<AlbumData>();
+ mImageList = new ArrayList<ImageData>(PHOTOS.length);
+ mAlbumList = new ArrayList<AlbumData>(1);
fillQueue();
}
@@ -67,8 +55,7 @@ public class StockSource extends PhotoSource {
data.id = ALBUM_ID;
data.account = mStockPhotoName;
data.title = mStockPhotoName;
- data.thumbnailUrl = mStockThumbnail;
- mAlbumList.offer(data);
+ mAlbumList.add(data);
}
log(TAG, "returning a list of albums: " + mAlbumList.size());
return mAlbumList;
@@ -80,7 +67,7 @@ public class StockSource extends PhotoSource {
for (int i = 0; i < PHOTOS.length; i++) {
ImageData data = new ImageData();
data.id = Integer.toString(PHOTOS[i]);
- mImageList.offer(data);
+ mImageList.add(data);
}
}
return mImageList;
@@ -100,3 +87,4 @@ public class StockSource extends PhotoSource {
return is;
}
}
+