diff --git a/app/build.gradle b/app/build.gradle index 31c7cc604..1b724e8c5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "com.hippo.ehviewer" - minSdkVersion 9 + minSdkVersion 14 targetSdkVersion 21 versionCode 1 versionName "1.0" diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index 817872bd5..4dcb0d369 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -16,13 +16,20 @@ package com.hippo.ehviewer.client; import android.os.AsyncTask; +import android.os.Process; import com.hippo.ehviewer.data.GalleryInfo; import com.hippo.ehviewer.network.EhHttpHelper; import com.hippo.network.ResponseCodeException; -import com.hippo.util.Utils; +import com.hippo.util.PriorityThreadFactory; -public class EhClient { +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public final class EhClient { @SuppressWarnings("unused") private static final String TAG = EhClient.class.getSimpleName(); @@ -46,33 +53,52 @@ public class EhClient { public static final String API_EHVIEWER = "http://www.ehviewer.com/API"; - private Object[] doGetGalleryList(int source, String url) throws Exception { - EhHttpHelper ehh = new EhHttpHelper(); - String body = ehh.get(url); - - final int responseCode = ehh.getResponseCode(); - if (responseCode >= 400) { - throw new ResponseCodeException(responseCode); - } + private final ThreadPoolExecutor mRequestThreadPool; - ListParser parser = new ListParser(); - parser.parse(body, source); - return new Object[]{parser.giArray, parser.pageNum}; + private static final EhClient sInstance; + + static { + sInstance = new EhClient(); + } + + public static EhClient getInstance() { + return sInstance; + } + + private EhClient() { + int poolSize = 3; + BlockingQueue requestWorkQueue = new LinkedBlockingQueue<>(); + ThreadFactory threadFactory = new PriorityThreadFactory(TAG, + Process.THREAD_PRIORITY_BACKGROUND); + mRequestThreadPool = new ThreadPoolExecutor(poolSize, poolSize, + 1, TimeUnit.SECONDS, requestWorkQueue, threadFactory); + } + + public static String getUrlHeader(int mode) { + switch (mode) { + default: + case SOURCE_G: + return HEADER_G; + case SOURCE_EX: + return HEADER_EX; + case SOURCE_LOFI: + return HEADER_LOFI; + } } public static interface EhClientListener { public void onFailure(Exception e); } - + public abstract static class OnGetGalleryListListener implements EhClientListener { public abstract void onSuccess(GalleryInfo[] glArray, int pageNum); } private void doBgJob(BgJobHelper bjh) { - Utils.execute(false, new AsyncTask() { + new AsyncTask() { @Override - protected BgJobHelper doInBackground(Object... params) { - BgJobHelper bjh = (BgJobHelper) params[0]; + protected BgJobHelper doInBackground(BgJobHelper... params) { + BgJobHelper bjh = params[0]; bjh.doInBackground(); return bjh; } @@ -81,7 +107,7 @@ public class EhClient { protected void onPostExecute(BgJobHelper bjh) { bjh.onPostExecute(); } - }, bjh); + }.executeOnExecutor(mRequestThreadPool, bjh); } private interface BgJobHelper { @@ -89,12 +115,12 @@ public class EhClient { public void onPostExecute(); } - + private abstract class SimpleBgJobHelper implements BgJobHelper { private EhClientListener mListener; private Exception mException; - + public SimpleBgJobHelper(EhClientListener listener) { mListener = listener; } @@ -124,7 +150,25 @@ public class EhClient { public abstract void doSuccessCallback(); } - + + private void checkRequest(EhHttpHelper ehh) throws ResponseCodeException { + final int responseCode = ehh.getResponseCode(); + if (responseCode >= 400) { + throw new ResponseCodeException(responseCode); + } + } + + private Object[] doGetGalleryList(int source, String url) throws Exception { + EhHttpHelper ehh = new EhHttpHelper(); + String body = ehh.get(url); + + checkRequest(ehh); + + ListParser parser = new ListParser(); + parser.parse(body, source); + return new Object[]{parser.giArray, parser.pageNum}; + } + private final class GetGalleryListHelper extends SimpleBgJobHelper { private int mSource; @@ -133,7 +177,7 @@ public class EhClient { private GalleryInfo[] mGlArray; private int mPageNum; - + public GetGalleryListHelper(int source, String url, OnGetGalleryListListener listener) { super(listener); mSource = source; @@ -156,7 +200,7 @@ public class EhClient { /** * Get gallery list - * + * * @param source the source, one of {@link #SOURCE_G}, {@link #SOURCE_EX} and * {@link #SOURCE_LOFI} * @param url the url to get gallery list diff --git a/app/src/main/java/com/hippo/ehviewer/data/ListUrlBuilder.java b/app/src/main/java/com/hippo/ehviewer/data/ListUrlBuilder.java new file mode 100644 index 000000000..026ceaa50 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/ListUrlBuilder.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2014-2015 Hippo Seven + * + * 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 + * 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.hippo.ehviewer.data; + +import com.hippo.ehviewer.client.EhClient; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.widget.AdvanceSearchTable; +import com.hippo.network.UrlBuilder; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public class ListUrlBuilder { + + // Mode + public static final int MODE_NORMAL = 0x0; + public static final int MODE_UPLOADER = 0x1; + public static final int MODE_TAG = 0x2; + public static final int MODE_POPULAR = 0x3; + public static final int MODE_IMAGE_SEARCH = 0x4; + + public static final int ALL_CATEGORT = EhUtils.UNKNOWN - 1; + //DOUJINSHI|MANGA|ARTIST_CG|GAME_CG|WESTERN|NON_H|IMAGE_SET|COSPLAY|ASIAN_PORN|MISC; + + public static final int DEFAULT_ADVANCE = AdvanceSearchTable.SNAME | AdvanceSearchTable.STAGS; + public static final int DEFAULT_MIN_RATING = 2; + + private int mMode = MODE_NORMAL; + + private int mPageIndex = 0; + + private int mCategory = EhUtils.NONE; + private String mSearchKey = null; + + private int mAdvanceSearch = -1; + private int mMinRating = -1; + + private String mSearchTag = null; + + public int getMode() { + return mMode; + } + + public void setMode(int mode) { + mMode = mode; + } + + public int getPageIndex() { + return mPageIndex; + } + + public void setPageIndex(int pageIndex) { + mPageIndex = pageIndex; + } + + public int getCategory() { + return mCategory; + } + + public void setCategory(int category) { + mCategory = category; + } + + public String getSearchKey() { + return mSearchKey; + } + + public void setSearchKey(String searchKey) { + mSearchKey = searchKey; + } + + public int getAdvanceSearch() { + return mAdvanceSearch; + } + + public void setAdvanceSearch(int advanceSearch) { + mAdvanceSearch = advanceSearch; + } + + public int getMinRating() { + return mMinRating; + } + + public void setMinRating(int minRating) { + mMinRating = minRating; + } + + public String getSearchTag() { + return mSearchTag; + } + + public void setSearchTag(String searchTag) { + mSearchTag = searchTag; + } + + /** + * Make them the same + * @param glub The template + */ + public void set(ListUrlBuilder glub) { + mMode = glub.mMode; + mPageIndex = glub.mPageIndex; + mCategory = glub.mCategory; + mSearchKey = glub.mSearchKey; + mAdvanceSearch = glub.mAdvanceSearch; + mMinRating = glub.mMinRating; + mSearchTag = glub.mSearchTag; + } + + public String build(int source) throws UnsupportedSearch { + switch (mMode) { + default: + case MODE_NORMAL: + case MODE_UPLOADER: { + boolean filter = false; + UrlBuilder ub = new UrlBuilder(EhClient.getUrlHeader(source)); + if (mCategory != EhUtils.NONE) { + ub.addQuery("f_doujinshi", ((mCategory & EhUtils.DOUJINSHI) == 0) ? "0" : "1"); + ub.addQuery("f_manga", ((mCategory & EhUtils.MANGA) == 0) ? "0" : "1"); + ub.addQuery("f_artistcg", ((mCategory & EhUtils.ARTIST_CG) == 0) ? "0" : "1"); + ub.addQuery("f_gamecg", ((mCategory & EhUtils.GAME_CG) == 0) ? "0" : "1"); + ub.addQuery("f_western", ((mCategory & EhUtils.WESTERN) == 0) ? "0" : "1"); + ub.addQuery("f_non-h", ((mCategory & EhUtils.NON_H) == 0) ? "0" : "1"); + ub.addQuery("f_imageset", ((mCategory & EhUtils.IMAGE_SET) == 0) ? "0" : "1"); + ub.addQuery("f_cosplay", ((mCategory & EhUtils.COSPLAY) == 0) ? "0" : "1"); + ub.addQuery("f_asianporn", ((mCategory & EhUtils.ASIAN_PORN) == 0) ? "0" : "1"); + ub.addQuery("f_misc", ((mCategory & EhUtils.MISC) == 0) ? "0" : "1"); + filter = true; + } + // Search key + if (mSearchKey != null) { + try { + ub.addQuery("f_search", URLEncoder.encode(mSearchKey, "UTF-8")); + filter = true; + } catch (UnsupportedEncodingException e) { + // Empty + } + } + // Page index + if (mPageIndex != 0) { + ub.addQuery("page", mPageIndex); + } + // Advance search + if (mAdvanceSearch != -1) { + ub.addQuery("advsearch", "1"); + if((mAdvanceSearch & AdvanceSearchTable.SNAME) != 0) ub.addQuery("f_sname", "on"); + if((mAdvanceSearch & AdvanceSearchTable.STAGS) != 0) ub.addQuery("f_stags", "on"); + if((mAdvanceSearch & AdvanceSearchTable.SDESC) != 0) ub.addQuery("f_sdesc", "on"); + if((mAdvanceSearch & AdvanceSearchTable.STORR) != 0) ub.addQuery("f_storr", "on"); + if((mAdvanceSearch & AdvanceSearchTable.STO) != 0) ub.addQuery("f_sto", "on"); + if((mAdvanceSearch & AdvanceSearchTable.SDT1) != 0) ub.addQuery("f_sdt1", "on"); + if((mAdvanceSearch & AdvanceSearchTable.SDT2) != 0) ub.addQuery("f_sdt2", "on"); + if((mAdvanceSearch & AdvanceSearchTable.SH) != 0) ub.addQuery("f_sh", "on"); + // Set min star + if (mMinRating != -1) { + ub.addQuery("f_sr", "on"); + ub.addQuery("f_srdd", mMinRating); + } + filter = true; + } + // Add filter foot + if (filter) { + ub.addQuery("f_apply", "Apply+Filter"); + } + return ub.build(); + } + case MODE_TAG: { + if (source == EhClient.SOURCE_LOFI) { + throw new UnsupportedSearch("Lofi do not support tag search"); + } + StringBuilder sb = new StringBuilder(EhClient.getUrlHeader(source)); + sb.append("tag/"); + try { + sb.append(URLEncoder.encode(mSearchTag, "UTF-8")); + } catch (UnsupportedEncodingException e) { + // Empty + } + return sb.toString(); + } + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/GalleryListUrlBuilder.java b/app/src/main/java/com/hippo/ehviewer/data/UnsupportedSearch.java similarity index 72% rename from app/src/main/java/com/hippo/ehviewer/data/GalleryListUrlBuilder.java rename to app/src/main/java/com/hippo/ehviewer/data/UnsupportedSearch.java index fd004e954..c260cf71d 100644 --- a/app/src/main/java/com/hippo/ehviewer/data/GalleryListUrlBuilder.java +++ b/app/src/main/java/com/hippo/ehviewer/data/UnsupportedSearch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2015 Hippo Seven + * Copyright (C) 2015 Hippo Seven * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,9 @@ package com.hippo.ehviewer.data; -public class GalleryListUrlBuilder { - - /** - * Make them the same - * @param glub The template - */ - public void set(GalleryListUrlBuilder glub) { +public class UnsupportedSearch extends Exception { + public UnsupportedSearch(String detailMessage) { + super(detailMessage); } - } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/fragment/SearchFragment.java b/app/src/main/java/com/hippo/ehviewer/ui/fragment/SearchFragment.java index 5afb9bae8..40bb9c5c6 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/fragment/SearchFragment.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/fragment/SearchFragment.java @@ -29,7 +29,7 @@ import android.view.View; import android.view.ViewGroup; import com.hippo.ehviewer.R; -import com.hippo.ehviewer.data.GalleryListUrlBuilder; +import com.hippo.ehviewer.data.ListUrlBuilder; import com.hippo.ehviewer.widget.SearchLayout; import com.hippo.scene.Scene; @@ -51,6 +51,10 @@ public class SearchFragment extends Fragment implements SearchLayout.SearhLayout sScene = scene; } + public static void setOnSearchListener(OnSearchListener listener) { + sOnSearchListener = listener; + } + @Override public void onViewStateRestored(Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); @@ -88,6 +92,13 @@ public class SearchFragment extends Fragment implements SearchLayout.SearhLayout sScene.startActivityForResult(intent, this); } + @Override + public void onRequestSearch(ListUrlBuilder lub) { + if (sOnSearchListener != null) { + sOnSearchListener.onRequestSearch(lub); + } + } + @Override public void onGetResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { @@ -108,6 +119,6 @@ public class SearchFragment extends Fragment implements SearchLayout.SearhLayout } public interface OnSearchListener { - public void onSearch(GalleryListUrlBuilder glub); + public void onRequestSearch(ListUrlBuilder lub); } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index 95b408b9a..49bc5fa59 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -23,7 +23,7 @@ import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import com.hippo.ehviewer.R; -import com.hippo.ehviewer.data.GalleryListUrlBuilder; +import com.hippo.ehviewer.data.ListUrlBuilder; import com.hippo.ehviewer.ui.fragment.GalleryListFragment; import com.hippo.ehviewer.ui.fragment.SearchFragment; import com.hippo.scene.Scene; @@ -53,6 +53,7 @@ public class GalleryListScene extends Scene implements SearchFragment.OnSearchLi mAppbar.setTitle(mResources.getString(R.string.app_name)); SearchFragment.setScene(this); + SearchFragment.setOnSearchListener(this); } @Override @@ -70,10 +71,11 @@ public class GalleryListScene extends Scene implements SearchFragment.OnSearchLi dispatchRemove(); SearchFragment.setScene(null); + SearchFragment.setOnSearchListener(null); } @Override - public void onSearch(GalleryListUrlBuilder glub) { + public void onRequestSearch(ListUrlBuilder lub) { } diff --git a/app/src/main/java/com/hippo/ehviewer/widget/AdvanceSearchTable.java b/app/src/main/java/com/hippo/ehviewer/widget/AdvanceSearchTable.java index 34fce9322..4849ad8f6 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/AdvanceSearchTable.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/AdvanceSearchTable.java @@ -26,6 +26,15 @@ import com.hippo.ehviewer.R; public class AdvanceSearchTable extends TableLayout { + public static final int SNAME = 0x1; + public static final int STAGS = 0x2; + public static final int SDESC = 0x4; + public static final int STORR = 0x8; + public static final int STO = 0x10; + public static final int SDT1 = 0x20; + public static final int SDT2 = 0x40; + public static final int SH = 0x80; + private CheckBox mSname; private CheckBox mStags; private CheckBox mSdesc; @@ -63,4 +72,25 @@ public class AdvanceSearchTable extends TableLayout { mMinRating = (Spinner) findViewById(R.id.search_min_rating); } + public int getAdvanceSearch() { + int advanceSearch = 0; + if (mSname.isChecked()) advanceSearch |= SNAME; + if (mStags.isChecked()) advanceSearch |= STAGS; + if (mSdesc.isChecked()) advanceSearch |= SDESC; + if (mStorr.isChecked()) advanceSearch |= STORR; + if (mSto.isChecked()) advanceSearch |= STO; + if (mSdt1.isChecked()) advanceSearch |= SDT1; + if (mSdt2.isChecked()) advanceSearch |= SDT2; + if (mSh.isChecked()) advanceSearch |= SH; + return advanceSearch; + } + + public int getMinRating() { + if (mSr.isChecked()) { + return mMinRating.getSelectedItemPosition() + 2; + } else { + return -1; + } + } + } diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java index 60213b61e..2f4fe04b8 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/SearchLayout.java @@ -23,6 +23,7 @@ import android.os.Parcelable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SwitchCompat; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; @@ -35,6 +36,7 @@ import android.widget.TextView; import com.hippo.effect.ripple.RippleSalon; import com.hippo.ehviewer.R; +import com.hippo.ehviewer.data.ListUrlBuilder; import com.hippo.util.ViewUtils; import com.hippo.widget.FloatLabelEditText; import com.hippo.widget.FloatingActionButton; @@ -42,6 +44,7 @@ import com.hippo.widget.PrefixEditText; import com.hippo.widget.recyclerview.EasyRecyclerView; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class SearchLayout extends FrameLayout implements CompoundButton.OnCheckedChangeListener, View.OnClickListener, SelectSearchImageLayout.SelectSearchImageLayoutHelper { @@ -104,6 +107,8 @@ public class SearchLayout extends FrameLayout implements CompoundButton.OnChecke private SearhLayoutHelper mHelper; + private ListUrlBuilder mTempListUrlBuilder = new ListUrlBuilder(); + private Bitmap mSearchImage; public SearchLayout(Context context) { @@ -137,6 +142,8 @@ public class SearchLayout extends FrameLayout implements CompoundButton.OnChecke mSearchContainer.setAdapter(mAdapter); mSearchContainer.setHasFixedSize(true); mSearchContainer.setItemAnimator(mAnimator); + + mFab.setOnClickListener(this); } public void setHelper(SearhLayoutHelper helper) { @@ -213,6 +220,10 @@ public class SearchLayout extends FrameLayout implements CompoundButton.OnChecke // TODO add quick search } else if (v == mAction2) { taggleSearchMode(); + } else if (v == mFab) { + if (mHelper != null) { + mHelper.onRequestSearch(getListUrlBuilder(null)); + } } } @@ -227,6 +238,45 @@ public class SearchLayout extends FrameLayout implements CompoundButton.OnChecke } } + public ListUrlBuilder getListUrlBuilder(@Nullable ListUrlBuilder lub) { + if (lub == null) { + lub = mTempListUrlBuilder; + } + + switch (mSearchMode) { + default: + case SEARCH_MODE_NORMAL: + lub.setMode(ListUrlBuilder.MODE_NORMAL); + if (mTableCategory != null) { + lub.setCategory(mTableCategory.getCategory()); + } + if (mTextSearch != null) { + String searchKey = mTextSearch.getText().toString(); + lub.setSearchKey(TextUtils.isEmpty(searchKey) ? null : searchKey); + } + if (mSwitchEnableAdvance.isChecked() && mTableAdvanceSearch != null) { + lub.setAdvanceSearch(mTableAdvanceSearch.getAdvanceSearch()); + lub.setMinRating(mTableAdvanceSearch.getMinRating()); + } else { + lub.setAdvanceSearch(-1); + lub.setMinRating(-1); + } + break; + case SEARCH_MODE_TAG: + lub.setMode(ListUrlBuilder.MODE_TAG); + if (mEditTextTag != null) { + lub.setSearchTag(mEditTextTag.getText().toString()); + } + break; + case SEARCH_MODE_IMAGE: + lub.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH); + // TODO + break; + } + + return lub; + } + private class SearchHolder extends RecyclerView.ViewHolder { public TextView title; @@ -418,5 +468,7 @@ public class SearchLayout extends FrameLayout implements CompoundButton.OnChecke public interface SearhLayoutHelper { public void onRequestSelectImage(); + + public void onRequestSearch(ListUrlBuilder lub); } } diff --git a/app/src/main/java/com/hippo/util/PriorityThreadFactory.java b/app/src/main/java/com/hippo/util/PriorityThreadFactory.java new file mode 100644 index 000000000..8c33eb65d --- /dev/null +++ b/app/src/main/java/com/hippo/util/PriorityThreadFactory.java @@ -0,0 +1,49 @@ +/* + * 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.hippo.util; + + +import android.os.Process; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A thread factory that creates threads with a given thread priority. + */ +public class PriorityThreadFactory implements ThreadFactory { + + private final int mPriority; + private final AtomicInteger mNumber = new AtomicInteger(); + private final String mName; + + public PriorityThreadFactory(String name, int priority) { + mName = name; + mPriority = priority; + } + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, mName + '-' + mNumber.getAndIncrement()) { + @Override + public void run() { + Process.setThreadPriority(mPriority); + super.run(); + } + }; + } + +} diff --git a/app/src/main/java/com/hippo/widget/FloatLabelEditText.java b/app/src/main/java/com/hippo/widget/FloatLabelEditText.java index ab8de8e77..8a5bb2497 100644 --- a/app/src/main/java/com/hippo/widget/FloatLabelEditText.java +++ b/app/src/main/java/com/hippo/widget/FloatLabelEditText.java @@ -170,6 +170,10 @@ public class FloatLabelEditText extends FrameLayout { } } + public Editable getText() { + return mEditText.getText(); + } + private TextWatcher mTextWatcher = new TextWatcher() { @Override