package io.evercam.androidapp;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.LinearLayout;
import com.logentries.android.AndroidLogger;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.RejectedExecutionException;
import io.evercam.androidapp.authentication.EvercamAccount;
import io.evercam.androidapp.custom.CameraLayout;
import io.evercam.androidapp.custom.CustomProgressDialog;
import io.evercam.androidapp.custom.CustomScrollView;
import io.evercam.androidapp.custom.CustomScrollView.OnScrollStoppedListener;
import io.evercam.androidapp.custom.CustomedDialog;
import io.evercam.androidapp.dto.AppData;
import io.evercam.androidapp.dto.AppUser;
import io.evercam.androidapp.dto.EvercamCamera;
import io.evercam.androidapp.dto.ImageLoadingStatus;
import io.evercam.androidapp.feedback.KeenHelper;
import io.evercam.androidapp.feedback.LoadTimeFeedbackItem;
import io.evercam.androidapp.tasks.CheckInternetTask;
import io.evercam.androidapp.tasks.LoadCameraListTask;
import io.evercam.androidapp.utils.Commons;
import io.evercam.androidapp.utils.Constants;
import io.evercam.androidapp.utils.PrefsManager;
import io.evercam.androidapp.utils.PropertyReader;
import io.evercam.androidapp.video.MultiCameraActivity;
import io.keen.client.java.KeenClient;
public class CamerasActivity extends ParentActivity
{
public static CamerasActivity activity = null;
public MenuItem refresh;
private static final String TAG = "CamerasActivity";
public static int camerasPerRow = 2;
public boolean reloadCameraList = false;
public CustomProgressDialog reloadProgressDialog;
/**
* For user data collection, calculate how long it takes to load camera list
*/
private Date startTime;
private float databaseLoadTime = 0;
private AndroidLogger logger;
private KeenClient client;
private enum InternetCheckType
{
START, RESTART
}
private String usernameOnStop = "";
private boolean showOfflineOnStop;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if(this.getActionBar() != null)
{
this.getActionBar().setHomeButtonEnabled(true);
this.getActionBar().setDisplayShowTitleEnabled(false);
}
setContentView(R.layout.camslayoutwithslide);
initDataCollectionObjects();
activity = this;
checkUser();
/**
* Use Handler here because we want the title bar/menu get loaded first.
* When the app starts, it will load cameras to grid view twice:
* 1. Load cameras that saved locally without image (disabled load image from cache
* because it blocks UI.)
* 2. When camera list returned from Evercam, show them on screen with thumbnails,
* then request for snapshots in background separately.
*
* TODO: Check is it really necessary to keep the post delay handler here
* See if refresh icon stop animating or not.
*/
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
/**
* Sometimes Evercam returns the list less than 0.1 sec?
* so check it's returned or not before
* the first load to avoid loading it twice.
*/
io.evercam.androidapp.custom.FlowLayout camsLineView = (io.evercam.androidapp.custom.FlowLayout) CamerasActivity.this.findViewById(R.id.cameras_flow_layout);
if(!(camsLineView.getChildCount() > 0))
{
addAllCameraViews(false, false);
if(camsLineView.getChildCount() > 0 && databaseLoadTime == 0 && startTime != null)
{
databaseLoadTime = Commons.calculateTimeDifferenceFrom(startTime);
}
}
}
}, 1);
// Start loading camera list after menu created(because need the menu
// showing as animation)
new CamerasCheckInternetTask(CamerasActivity.this, InternetCheckType.START).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// draw the options defined in the following file
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.camera_list_menu, menu);
refresh = menu.findItem(R.id.menurefresh);
refresh.setActionView(R.layout.actionbar_indeterminate_progress);
return true;
}
// Tells that the item has been selected from the menu. Now check and get
// the selected item and perform the relevant action
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int itemId = item.getItemId();
if(itemId == R.id.menurefresh)
{
EvercamPlayApplication.sendEventAnalytics(this, R.string.category_menu, R.string.action_refresh, R.string.label_list_refresh);
if(refresh != null) refresh.setActionView(R.layout.actionbar_indeterminate_progress);
startCameraLoadingTask();
}
else if(itemId == R.id.menu_add_camera)
{
showAddCameraOptionsDialog();
}
else if(itemId == R.id.menu_settings)
{
EvercamPlayApplication.sendEventAnalytics(this, R.string.category_menu, R.string.action_settings, R.string.label_settings);
startActivity(new Intent(CamerasActivity.this, CameraPrefsActivity.class));
}
else if(itemId == R.id.menu_manage_accounts)
{
EvercamPlayApplication.sendEventAnalytics(this, R.string.category_menu, R.string.action_manage_account, R.string.label_account);
startActivityForResult(new Intent(CamerasActivity.this, ManageAccountsActivity.class), Constants.REQUEST_CODE_MANAGE_ACCOUNT);
}
else if(itemId == R.id.menu_logout)
{
showSignOutDialog();
}
else if(itemId == R.id.menu_feedback)
{
startActivity(new Intent(CamerasActivity.this, FeedbackActivity.class));
}
else if(itemId == R.id.menu_test)
{
startActivity(new Intent(this, MultiCameraActivity.class));
}
else
{
return super.onOptionsItemSelected(item);
}
return true;
}
@Override
public void onRestart()
{
super.onRestart();
if(MainActivity.isUserLogged(this))
{
//Reload camera list if default user has been changed, or offline settings has been changed
if(isUserChanged() || isOfflineSettingChanged())
{
new CamerasCheckInternetTask(CamerasActivity.this, InternetCheckType.START).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
else
{
try
{
new CamerasCheckInternetTask(CamerasActivity.this, InternetCheckType.RESTART).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
catch(RejectedExecutionException e)
{
EvercamPlayApplication.sendCaughtExceptionNotImportant(activity, e);
}
} usernameOnStop = "";
}
else
{
startActivity(new Intent(this, SlideActivity.class));
finish();
}
}
private boolean isUserChanged()
{
String restartedUsername = AppData.defaultUser.getUsername();
return !usernameOnStop.isEmpty() && !usernameOnStop.equals(restartedUsername);
}
private boolean isOfflineSettingChanged()
{
return showOfflineOnStop != PrefsManager.showOfflineCameras(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(requestCode == Constants.REQUEST_CODE_ADD_CAMERA)
{
reloadCameraList = (resultCode == Constants.RESULT_TRUE);
}
else if (requestCode == Constants.REQUEST_CODE_DELETE_CAMERA)
{
// Don't reset reload variable to false because it's possible set to TRUE when
// return from shortcut live view
if(resultCode == Constants.RESULT_TRUE)
{
reloadCameraList = true;
}
}
else if(requestCode == Constants.REQUEST_CODE_MANAGE_ACCOUNT)
{
reloadCameraList = (resultCode == Constants.RESULT_ACCOUNT_CHANGED);
}
}
private void startLoadingCameras()
{
reloadProgressDialog = new CustomProgressDialog(this);
if(reloadCameraList)
{
reloadProgressDialog.show(getString(R.string.loading_cameras));
}
startCameraLoadingTask();
}
private void checkUser()
{
if(AppData.defaultUser == null)
{
AppData.defaultUser = new EvercamAccount(this).getDefaultUser();
}
}
private void startCameraLoadingTask()
{
if(Commons.isOnline(this))
{
LoadCameraListTask loadTask = new LoadCameraListTask(AppData.defaultUser,
CamerasActivity.this);
loadTask.reload = true; // be default do not refresh until there
// is
// any change in cameras in database
loadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
else
{
CustomedDialog.showInternetNotConnectDialog(CamerasActivity.this);
}
}
// Stop All Camera Views
public void stopAllCameraViews()
{
io.evercam.androidapp.custom.FlowLayout camsLineView = (io.evercam.androidapp.custom
.FlowLayout) this.findViewById(R.id.cameras_flow_layout);
for(int count = 0; count < camsLineView.getChildCount(); count++)
{
LinearLayout linearLayout = (LinearLayout) camsLineView.getChildAt(count);
CameraLayout cameraLayout = (CameraLayout) linearLayout.getChildAt(0);
cameraLayout.stopAllActivity();
}
}
boolean resizeCameras()
{
try
{
int screen_width = readScreenWidth(this);
camerasPerRow = recalculateCameraPerRow();
io.evercam.androidapp.custom.FlowLayout camsLineView = (io.evercam.androidapp.custom
.FlowLayout) this.findViewById(R.id.cameras_flow_layout);
for(int i = 0; i < camsLineView.getChildCount(); i++)
{
LinearLayout pview = (LinearLayout) camsLineView.getChildAt(i);
CameraLayout cameraLayout = (CameraLayout) pview.getChildAt(0);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(android.view
.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams
.WRAP_CONTENT);
params.width = ((i + 1 % camerasPerRow == 0) ? (screen_width - (i %
camerasPerRow) * (screen_width / camerasPerRow)) : screen_width /
camerasPerRow);
params.width = params.width - 1; //1 pixels spacing between cameras
params.height = (int) (params.width / (1.25));
params.setMargins(1, 1, 0, 0); //1 pixels spacing between cameras
cameraLayout.setLayoutParams(params);
}
return true;
}
catch(Exception e)
{
Log.e(TAG, e.toString() + "::" + Log.getStackTraceString(e));
sendToMint(e);
EvercamPlayApplication.sendCaughtException(this, e);
CustomedDialog.showUnexpectedErrorDialog(CamerasActivity.this);
}
return false;
}
private void updateCameraNames()
{
try
{
io.evercam.androidapp.custom.FlowLayout camsLineView = (io.evercam.androidapp.custom
.FlowLayout) this.findViewById(R.id.cameras_flow_layout);
for(int i = 0; i < camsLineView.getChildCount(); i++)
{
LinearLayout pview = (LinearLayout) camsLineView.getChildAt(i);
CameraLayout cameraLayout = (CameraLayout) pview.getChildAt(0);
cameraLayout.updateTitleIfDifferent();
}
}
catch(Exception e)
{
Log.e(TAG, e.toString());
EvercamPlayApplication.sendCaughtException(this, e);
}
}
// Remove all the cameras so that all activities being performed can be
// stopped
public boolean removeAllCameraViews()
{
try
{
stopAllCameraViews();
io.evercam.androidapp.custom.FlowLayout camsLineView = (io.evercam.androidapp.custom
.FlowLayout) this.findViewById(R.id.cameras_flow_layout);
camsLineView.removeAllViews();
return true;
}
catch(Exception e)
{
Log.e(TAG, e.toString() + "::" + Log.getStackTraceString(e));
sendToMint(e);
EvercamPlayApplication.sendCaughtException(this, e);
CustomedDialog.showUnexpectedErrorDialog(CamerasActivity.this);
}
return false;
}
/**
* Add all camera views to the main grid page
*
* @param reloadImages reload camera images or not
* @param showThumbnails show thumbnails that returned by Evercam or not, if true
* and if thumbnail not available, it will request latest snapshot
* instead. If false,
* it will request neither thumbnail nor latest snapshot.
*/
public boolean addAllCameraViews(final boolean reloadImages, final boolean showThumbnails)
{
try
{
// Recalculate camera per row
camerasPerRow = recalculateCameraPerRow();
final CustomScrollView scrollView = (CustomScrollView) this.findViewById(R.id
.cameras_scroll_view);
io.evercam.androidapp.custom.FlowLayout camsLineView = (io.evercam.androidapp.custom
.FlowLayout) this.findViewById(R.id.cameras_flow_layout);
final Rect bounds = readLiveBoundsOfScrollView();
int screen_width = readScreenWidth(this);
int index = 0;
for(final EvercamCamera evercamCamera : AppData.evercamCameraList)
{
//Don't show offline camera
if(!PrefsManager.showOfflineCameras(this) && !evercamCamera.isActive())
{
continue;
}
final LinearLayout cameraListLayout = new LinearLayout(this);
int indexPlus = index + 1;
if(reloadImages) evercamCamera.loadingStatus = ImageLoadingStatus.not_started;
final CameraLayout cameraLayout = new CameraLayout(this, evercamCamera,
showThumbnails);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(android.view
.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams
.WRAP_CONTENT);
params.width = ((indexPlus % camerasPerRow == 0) ? (screen_width - (index %
camerasPerRow) * (screen_width / camerasPerRow)) : screen_width /
camerasPerRow);
params.width = params.width - 1; //1 pixels spacing between cameras
params.height = (int) (params.width / (1.25));
params.setMargins(0, 0, 0, 0); //No spacing between cameras
cameraLayout.setLayoutParams(params);
cameraListLayout.addView(cameraLayout);
camsLineView.addView(cameraListLayout, new io.evercam.androidapp.custom
.FlowLayout.LayoutParams(0, 0));
index++;
//TODO: Re-enable or remove this
// /**
// * If need to reload the images, read camera layout position and
// * check the rectangle is within scope of the screen or not
// */
// if(reloadImages)
// {
// evercamCamera.loadingStatus = ImageLoadingStatus.not_started;
// new Handler().postDelayed(new Runnable()
// {
// @Override
// public void run()
// {
// Rect cameraBounds = new Rect();
// cameraListLayout.getHitRect(cameraBounds);
// if(Rect.intersects(cameraBounds, bounds))
// {
// //FIXME
// //cameraLayout.loadImage();
// //cameraLayout.showThumbnail();
// }
// }
// }, 300);
// }
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
Rect cameraBounds = new Rect();
cameraListLayout.getHitRect(cameraBounds);
Rect offlineIconBounds = cameraLayout.getOfflineIconBounds();
int layoutWidth = cameraBounds.right - cameraBounds.left;
int offlineStartsAt = offlineIconBounds.left;
int offlineIconWidth = offlineIconBounds.right - offlineIconBounds.left;
if(layoutWidth > offlineStartsAt + offlineIconWidth*2)
{
cameraLayout.showOfflineIconAsFloat = false;
}
else
{
cameraLayout.showOfflineIconAsFloat = true;
}
}
}, 200);
}
if(this.getActionBar() != null) this.getActionBar().setHomeButtonEnabled(true);
if(refresh != null) refresh.setActionView(null);
//TODO: Re-enable or remove this
// // Only set up scroll listener if snapshots need to get reload
// if(reloadImages)
// {
// setScrollStopListenerFor(scrollView);
// }
return true;
}
catch(Exception e)
{
Log.e(TAG, e.toString(), e);
sendToMint(e);
EvercamPlayApplication.sendCaughtException(this, e);
CustomedDialog.showUnexpectedErrorDialog(CamerasActivity.this);
}
return false;
}
@Override
protected void onDestroy()
{
super.onDestroy();
activity = null;
removeAllCameraViews();
}
/**
* If screen get scrolled, for the moment of scroll stopping, load camera
* snapshots within screen.
*/
private void onScreenScrolled()
{
final Rect scrollViewBounds = readLiveBoundsOfScrollView();
final io.evercam.androidapp.custom.FlowLayout camsLineView1 = (io.evercam.androidapp
.custom.FlowLayout) CamerasActivity.this.findViewById(R.id.cameras_flow_layout);
final int totalLayouts = camsLineView1.getChildCount();
new Thread(new Runnable()
{
@Override
public void run()
{
for(int index = 0; index < totalLayouts; index++)
{
final LinearLayout cameraListLayout = (LinearLayout) camsLineView1.getChildAt
(index);
final CameraLayout cameraLayout = (CameraLayout) cameraListLayout.getChildAt(0);
if(cameraLayout.evercamCamera.loadingStatus == ImageLoadingStatus.not_started)
{
Rect cameraBounds = new Rect();
cameraListLayout.getHitRect(cameraBounds);
if(Rect.intersects(cameraBounds, scrollViewBounds))
{
CamerasActivity.this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
//FIXME
//cameraLayout.loadImage();
//cameraLayout.showThumbnail();
}
});
}
}
}
}
}).start();
}
private void setScrollStopListenerFor(final CustomScrollView scrollView)
{
scrollView.setOnTouchListener(new OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_UP)
{
scrollView.startScrollerTask();
}
return false;
}
});
scrollView.setOnScrollStoppedListener(new OnScrollStoppedListener()
{
@Override
public void onScrollStopped()
{
onScreenScrolled();
}
});
}
@Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
resizeCameras();
}
@Override
public void onStop()
{
super.onStop();
if(AppData.defaultUser != null)
{
usernameOnStop = AppData.defaultUser.getUsername();
}
showOfflineOnStop = PrefsManager.showOfflineCameras(this);
}
private void showAddCameraOptionsDialog()
{
final View optionsView = getLayoutInflater().inflate(R.layout.add_camera_options_list,
null);
final AlertDialog dialog = CustomedDialog.getAlertDialogNoTitle(CamerasActivity.this,
optionsView);
dialog.show();
Button addCameraButton = (Button) optionsView.findViewById(R.id.btn_add_ip_camera);
Button scanCameraButton = (Button) optionsView.findViewById(R.id.btn_scan_for_camera);
addCameraButton.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
dialog.dismiss();
EvercamPlayApplication.sendEventAnalytics(CamerasActivity.this, R.string
.category_menu, R.string.action_add_camera, R.string
.label_add_camera_manually);
startActivityForResult(new Intent(CamerasActivity.this, AddEditCameraActivity
.class), Constants.REQUEST_CODE_ADD_CAMERA);
}
});
scanCameraButton.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
dialog.dismiss();
EvercamPlayApplication.sendEventAnalytics(CamerasActivity.this, R.string
.category_menu, R.string.action_add_camera, R.string.label_add_camera_scan);
startActivityForResult(new Intent(CamerasActivity.this, ScanActivity.class),
Constants.REQUEST_CODE_ADD_CAMERA);
}
});
}
public static void logOutUser(Activity activity)
{
new EvercamAccount(activity).remove(AppData.defaultUser.getEmail(), null);
// clear real-time default app data
AppData.reset();
activity.finish();
activity.startActivity(new Intent(activity, SlideActivity.class));
}
private void showSignOutDialog()
{
CustomedDialog.getConfirmLogoutDialog(this, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
EvercamPlayApplication.sendEventAnalytics(CamerasActivity.this,
R.string.category_menu, R.string.action_logout, R.string.label_user_logout);
logOutUser(CamerasActivity.this);
}
}).show();
}
public static int readScreenWidth(Activity activity)
{
Display display = activity.getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size.x;
}
public static int readScreenHeight(Activity activity)
{
Display display = activity.getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size.y;
}
/**
* Recalculate camera per row preference for the following situations:
* 1. If it won't influence others and the current user only has one or two cameras,
* reset the value to be 1.
* 2. If current user only has one or two cameras, but the device has other accounts
* logged in, keep the value as it was without overriding it.
* 3. If the current user has more than two cameras, adjust the value of camera per
* row to be a proper number based on screen size.
*
* @return The recalculated value of camera per row
*/
public int recalculateCameraPerRow()
{
int totalCameras = AppData.evercamCameraList.size();
boolean isInfluencingOtherUser = false;
ArrayList<AppUser> userList = new EvercamAccount(this).retrieveUserList();
if(userList.size() > 1)
{
isInfluencingOtherUser = true;
}
if(totalCameras != 0 && totalCameras <= 2)
{
if(!isInfluencingOtherUser)
{
PrefsManager.setCameraPerRow(this, 1);
return 1;
}
else
{
return PrefsManager.getCameraPerRow(this, 2);
}
}
else
{
int screenWidth = readScreenWidth(this);
int maxCamerasPerRow = 3;
int minCamerasPerRow = 1;
if(screenWidth != 0)
{
maxCamerasPerRow = screenWidth / 350;
}
int oldCamerasPerRow = PrefsManager.getCameraPerRow(this, 2);
if(maxCamerasPerRow < oldCamerasPerRow && maxCamerasPerRow != 0)
{
PrefsManager.setCameraPerRow(this, maxCamerasPerRow);
return maxCamerasPerRow;
}
else if(maxCamerasPerRow == 0)
{
return minCamerasPerRow;
}
return oldCamerasPerRow;
}
}
private Rect readLiveBoundsOfScrollView()
{
CustomScrollView scrollView = (CustomScrollView) CamerasActivity.this.findViewById(R.id
.cameras_scroll_view);
return scrollView.getLiveBoundsRect();
}
private void initDataCollectionObjects()
{
startTime = new Date();
String logentriesToken = getPropertyReader()
.getPropertyStr(PropertyReader.KEY_LOGENTRIES_TOKEN);
if(!logentriesToken.isEmpty())
{
logger = AndroidLogger.getLogger(getApplicationContext(), logentriesToken, false);
}
client = KeenHelper.getClient(this);
}
/**
* Calculate how long it takes for the user to see the camera list
*/
public void calculateLoadingTimeAndSend()
{
if(startTime != null)
{
float timeDifferenceFloat = Commons.calculateTimeDifferenceFrom(startTime);
Log.d(TAG, "It takes " + databaseLoadTime + " and " + timeDifferenceFloat + " seconds" +
" to load camera list");
startTime = null;
String username = "";
if(AppData.defaultUser != null)
{
username = AppData.defaultUser.getUsername();
}
LoadTimeFeedbackItem feedbackItem = new LoadTimeFeedbackItem(this,
username, databaseLoadTime, timeDifferenceFloat);
databaseLoadTime = 0;
sendToLogentries(logger, feedbackItem.toJson());
feedbackItem.sendToKeenIo(client);
}
}
class CamerasCheckInternetTask extends CheckInternetTask
{
InternetCheckType type;
public CamerasCheckInternetTask(Context context, InternetCheckType type)
{
super(context);
this.type = type;
}
@Override
protected void onPostExecute(Boolean hasNetwork)
{
if(hasNetwork)
{
if(type == InternetCheckType.START)
{
startLoadingCameras();
}
else if(type == InternetCheckType.RESTART)
{
if(reloadCameraList)
{
removeAllCameraViews();
startLoadingCameras();
reloadCameraList = false;
}
else
{
// Re-calculate camera per row because screen size
// could change because of screen rotation.
int camsOldValue = camerasPerRow;
camerasPerRow = recalculateCameraPerRow();
if(camsOldValue != camerasPerRow)
{
removeAllCameraViews();
addAllCameraViews(true, true);
}
// Refresh camera names in case it's changed from camera
// live view
updateCameraNames();
}
}
}
else
{
CustomedDialog.showInternetNotConnectDialog(CamerasActivity.this);
}
}
}
}