/******************************************************************************
* Copyright (C) 2012, 2013, 2014, 2015, 2016
* Younghyung Cho. <yhcting77@gmail.com>
* All rights reserved.
*
* This file is part of NetMBuddy
*
* This program is licensed under the FreeBSD license
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the FreeBSD Project.
*****************************************************************************/
package free.yhc.netmbuddy;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import java.io.IOException;
import java.net.ConnectException;
import free.yhc.abaselib.AppEnv;
import free.yhc.baselib.Logger;
import free.yhc.baselib.exception.BadResponseException;
import free.yhc.abaselib.util.AUtil;
import free.yhc.netmbuddy.core.PolicyConstant;
import free.yhc.netmbuddy.core.SearchSuggestionProvider;
import free.yhc.netmbuddy.core.TaskManager;
import free.yhc.netmbuddy.core.YTDataAdapter;
import free.yhc.netmbuddy.core.YTPlayer;
import free.yhc.netmbuddy.task.YTVideoListTask;
public abstract class YTSearchActivity extends Activity {
private static final boolean DBG = Logger.DBG_DEFAULT;
private static final Logger P = Logger.create(YTSearchActivity.class, Logger.LOGLV_DEFAULT);
public static final String KEY_TITLE = "searctitle";
public static final String KEY_TEXT = "searchtext";
public static final String KEY_CUR_PAGETOKEN = "cur_pagetoken";
public static final String KEY_NEXT_PAGETOKEN = "next_pagetoken";
public static final String KEY_PREV_PAGETOKEN = "prev_pagetoken";
protected final YTPlayer mMp = YTPlayer.get();
protected final TaskManager mTm = TaskManager.get();
protected ListView mListv = null;
private String mText = null;
private String mTitle = null;
private String mCurPageToken = null;
private String mNextPageToken = null;
private String mPrevPageToken = null;
private YTVideoListTask mVLTask = null;
private final YTVideoListTask.EventListener<YTVideoListTask, YTDataAdapter.VideoListResp> mVLTaskListener
= new YTVideoListTask.EventListener<YTVideoListTask, YTDataAdapter.VideoListResp>() {
@Override
public void
onPostRun(@NonNull YTVideoListTask task,
YTDataAdapter.VideoListResp result,
Exception ex) {
Err err = Err.NO_ERR;
if (null != ex) {
/* This may be slower than instanceof... But, it's easy to read
* This is literally, exceptional case. So, it doesn't happened frequently.
*/
try { throw ex; }
catch (ConnectException e) {
err = Err.NETWORK_UNAVAILABLE;
} catch (IOException | BadResponseException e) {
err = Err.IO_NET;
} catch (InterruptedException e) {
err = Err.CANCELLED;
} catch (Exception e) {
if (DBG) P.w(P.stackTrace(e));
err = Err.UNKNOWN;
}
}
handleYtVideoListResponse(task, task.getRequest(), result, err);
}
};
// ========================================================================
//
// UI Control
//
// ========================================================================
protected void
setImageView(ImageView iv,
int drawable,
View.OnClickListener onClick) {
if (drawable <= 0 || null == onClick) {
iv.setVisibility(View.GONE);
iv.setOnClickListener(null);
} else {
iv.setImageResource(drawable);
iv.setVisibility(View.VISIBLE);
iv.setOnClickListener(onClick);
}
}
protected void
setTitleText(String text) {
TextView tv = (TextView)findViewById(R.id.title);
tv.setText(text);
}
@SuppressWarnings("unused")
protected void
setTitleText(int resid) {
TextView tv = (TextView)findViewById(R.id.title);
tv.setText(resid);
}
protected ViewGroup
disableContent() {
ViewGroup content = (ViewGroup)findViewById(R.id.content);
TextView tv = (TextView)content.findViewById(R.id.content_txt);
ImageView iv = (ImageView)content.findViewById(R.id.content_loading);
ListView lv = (ListView)content.findViewById(R.id.content_list);
tv.setVisibility(View.INVISIBLE);
if (null != iv.getAnimation()) {
iv.getAnimation().cancel();
iv.setAnimation(null);
}
iv.setVisibility(View.INVISIBLE);
lv.setVisibility(View.INVISIBLE);
content.setVisibility(View.INVISIBLE);
return content;
}
protected TextView
enableContentText(int resid) {
return enableContentText(AUtil.getResString(resid));
}
protected TextView
enableContentText(String text) {
View content = disableContent();
content.setVisibility(View.VISIBLE);
TextView tv = (TextView)content.findViewById(R.id.content_txt);
tv.setVisibility(View.VISIBLE);
tv.setText(text);
return tv;
}
protected ImageView
enableContentLoading() {
View content = disableContent();
content.setVisibility(View.VISIBLE);
ImageView iv = (ImageView)content.findViewById(R.id.content_loading);
iv.setVisibility(View.VISIBLE);
iv.startAnimation(AnimationUtils.loadAnimation(this, R.anim.rotate));
return iv;
}
protected ListView
enableContentList() {
View content = disableContent();
content.setVisibility(View.VISIBLE);
mListv.setVisibility(View.VISIBLE);
return mListv;
}
private ViewGroup
disableNaviBar() {
LinearLayout nb = (LinearLayout)findViewById(R.id.navibar);
nb.setVisibility(View.INVISIBLE);
return nb;
}
protected ViewGroup
enableNaviBar(boolean prev, String text, boolean next) {
LinearLayout nb = (LinearLayout)findViewById(R.id.navibar);
nb.setVisibility(View.VISIBLE);
TextView tv = (TextView)nb.findViewById(R.id.navi_current);
tv.setText(text);
nb.findViewById(R.id.navi_prev).setVisibility(prev ? View.VISIBLE : View.INVISIBLE);
nb.findViewById(R.id.navi_next).setVisibility(next ? View.VISIBLE : View.INVISIBLE);
return nb;
}
protected void
setupToolBtn1(int drawable,
View.OnClickListener onClick) {
setImageView((ImageView) findViewById(R.id.toolbtn1), drawable, onClick);
}
protected void
setupToolBtn2(int drawable,
View.OnClickListener onClick) {
setImageView((ImageView) findViewById(R.id.toolbtn2), drawable, onClick);
}
protected void
setupBottomBar(int tool1Drawable,
View.OnClickListener tool1OnClick,
int tool2Drawable,
View.OnClickListener tool2OnClick) {
setupToolBtn1(tool1Drawable, tool1OnClick);
setupToolBtn2(tool2Drawable, tool2OnClick);
}
@SuppressWarnings("unused")
@Nullable
private YTSearchAdapter
getAdapter() {
if (null != mListv)
return (YTSearchAdapter)mListv.getAdapter();
return null;
}
// ========================================================================
//
//
//
// ========================================================================
protected abstract YTDataAdapter.ReqType
getSearchType();
protected abstract void
onListItemClick(View view, int position, long itemId);
protected abstract void
onSearchResponse(@NonNull YTVideoListTask ytvl,
@NonNull YTDataAdapter.VideoListReq req,
@NonNull YTDataAdapter.VideoListResp resp,
@NonNull Err err);
// ========================================================================
//
//
//
// ========================================================================
private static String
searchTypeString(YTDataAdapter.ReqType reqType) {
switch (reqType) {
case VID_KEYWORD:
return AUtil.getResString(R.string.keyword);
case VID_CHANNEL:
return AUtil.getResString(R.string.channel);
}
P.bug(false);
return null;
}
private void
handleYtVideoListResponse(
@NonNull YTVideoListTask ytvl,
@NonNull YTDataAdapter.VideoListReq req,
@NonNull YTDataAdapter.VideoListResp resp,
@NonNull Err err) {
P.bug(null != mVLTask);
if (mVLTask != ytvl)
return; // new Task is already started! Ignore this result.
if (!handleSearchResult(ytvl, req, resp, err))
return; // There is an error in search
onSearchResponse(ytvl, req, resp, err);
}
private boolean
handleSearchResult(@NonNull @SuppressWarnings("unused") YTVideoListTask ytvl,
@NonNull @SuppressWarnings("unused") YTDataAdapter.VideoListReq req,
@NonNull YTDataAdapter.VideoListResp resp,
@NonNull Err err) {
P.bug(AUtil.isUiThread());
if (Err.NO_ERR == err
&& resp.vids.length <= 0)
err = Err.NO_MATCH;
if (Err.NO_ERR != err) {
mPrevPageToken = mNextPageToken = null;
enableContentText(err.getMessage());
return false;
}
mPrevPageToken = resp.page.prevPageToken;
mNextPageToken = resp.page.nextPageToken;
enableNaviBar(null != mPrevPageToken, "", null != mNextPageToken);
return true;
}
private void
loadPage(String title, String text, String pageToken) {
disableNaviBar();
// close helper to cancel all existing work.
if (null != mVLTask) {
mVLTask.removeEventListener(mVLTaskListener);
mTm.cancelTask(mVLTask, null);
}
mTitle = title;
mText = text;
mCurPageToken = pageToken;
YTDataAdapter.VideoListReq ytreq = new YTDataAdapter.VideoListReq(
getSearchType(),
text,
pageToken,
PolicyConstant.YTSEARCH_MAX_RESULTS);
mVLTask = YTVideoListTask.create(ytreq, null);
mVLTask.addEventListener(AppEnv.getUiHandlerAdapter(), mVLTaskListener);
setTitleText(searchTypeString(getSearchType()) + " : " + title);
if (mTm.addTask(mVLTask,
mVLTask, // id
this, // type
null))
enableContentLoading();
else
enableContentText(Err.UNKNOWN.getMessage());
}
protected void
doNewSearch() {
onSearchRequested();
}
protected void
startNewSearch(final String title, final String text) {
loadPage(title, text, "");
}
// ========================================================================
//
// Overriding activity
//
// ========================================================================
@SuppressWarnings("unused")
private void
restoreInstanceState(Bundle b) {
if (null == b)
return;
mText = b.getString(KEY_TEXT);
mCurPageToken = b.getString(KEY_CUR_PAGETOKEN);
mPrevPageToken = b.getString(KEY_PREV_PAGETOKEN);
mNextPageToken = b.getString(KEY_NEXT_PAGETOKEN);
}
private static void
putBundleString(Bundle b, String key, String value) {
if (null != value)
b.putString(key, value);
}
@Override
public void
onSaveInstanceState(@NonNull Bundle b) {
putBundleString(b, KEY_TEXT, mText);
putBundleString(b, KEY_CUR_PAGETOKEN, mCurPageToken);
putBundleString(b, KEY_PREV_PAGETOKEN, mPrevPageToken);
putBundleString(b, KEY_NEXT_PAGETOKEN, mNextPageToken);
}
@Override
public void
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
}
@Override
public void
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ytsearch);
mListv = (ListView)findViewById(R.id.content_list);
registerForContextMenu(mListv);
mListv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void
onItemClick(AdapterView<?> parent, View view, int position, long itemId) {
onListItemClick(view, position, itemId);
}
});
findViewById(R.id.navi_prev).setOnClickListener(new View.OnClickListener() {
@Override
public void
onClick(View v) {
if (null == mPrevPageToken) {
P.bug(false); // This is UNEXPECTED.
return;
}
loadPage(mTitle, mText, mPrevPageToken);
}
});
findViewById(R.id.navi_next).setOnClickListener(new View.OnClickListener() {
@Override
public void
onClick(View v) {
if (null == mNextPageToken) {
P.bug(false); // This is UNEXPECTED.
return;
}
loadPage(mTitle, mText, mNextPageToken);
}
});
disableContent();
disableNaviBar();
}
@Override
protected void
onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
if (!Intent.ACTION_SEARCH.equals(intent.getAction()))
return; // ignore unexpected intent
final String query = intent.getStringExtra(SearchManager.QUERY);
SearchSuggestionProvider.saveRecentQuery(query);
disableContent();
disableNaviBar();
AppEnv.getUiHandler().post(new Runnable() {
@Override
public void
run() {
startNewSearch(query, query);
}
});
}
@Override
protected void
onResume() {
// onResume of each fragments SHOULD be called after 'setController'.
// So, super.onResume() is located at the bottom of onResume().
super.onResume();
ViewGroup playerv = (ViewGroup)findViewById(R.id.player);
mMp.setController(this,
playerv,
(ViewGroup)findViewById(R.id.list_drawer),
null,
mMp.getVideoToolButton());
if (mMp.hasActiveVideo())
playerv.setVisibility(View.VISIBLE);
else
playerv.setVisibility(View.GONE);
}
@Override
protected void
onPause() {
mMp.unsetController(this);
super.onPause();
}
@Override
protected void
onStop() {
super.onStop();
}
@Override
protected void
onDestroy() {
super.onDestroy();
}
@Override
public void
onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Do nothing!
}
@Override
public void
onBackPressed() {
super.onBackPressed();
}
}