package com.axzae.homeassistant; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v4.content.res.ResourcesCompat; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.animation.OvershootInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.StackingBehavior; import com.axzae.homeassistant.model.Entity; import com.axzae.homeassistant.model.ErrorMessage; import com.axzae.homeassistant.model.Group; import com.axzae.homeassistant.model.HomeAssistantServer; import com.axzae.homeassistant.provider.DatabaseManager; import com.axzae.homeassistant.provider.EntityWidgetProvider; import com.axzae.homeassistant.provider.ServiceProvider; import com.axzae.homeassistant.util.CommonUtil; import com.crashlytics.android.Crashlytics; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import java.io.File; import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Locale; import retrofit2.Response; /** * A login screen that offers login via username/password. */ public class ConnectActivity extends BaseActivity { public static final String EXTRA_IPADDRESS = "ip_address"; public static final String EXTRA_FULL_URI = "full_uri"; public static final String EXTRA_PASSWORD = "password"; public static final String EXTRA_LAST_REQUEST = "last_request"; private SharedPreferences mSharedPref; private int settingCountDown = 5; // UI references. private EditText mIpAddressView; private EditText mPasswordView; private TextView mTextProgress; private ProgressBar mProgressBar; private Snackbar mSnackbar; private Button mConnectButton; private UserLoginTask mAuthTask; private LinearLayout mLayoutMain; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_connect); mLayoutMain = findViewById(R.id.main_layout); mLayoutMain.setVisibility(View.GONE); //Send a Google Analytics screen view. //Tracker tracker = getAppController().getDefaultTracker(); //tracker.send(new HitBuilders.ScreenViewBuilder().build()); try { SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.ENGLISH); Log.d("YouQi", "Date1: " + df2.parse("2017-10-01T23:00:12+00:00").getTime()); } catch (Exception e) { e.printStackTrace(); Log.d("YouQi", "Date2: " + e.getMessage()); } findViewById(R.id.splash_logo).setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: ObjectAnimator scaleDownX = ObjectAnimator.ofFloat(v, "scaleX", 0.95f); ObjectAnimator scaleDownY = ObjectAnimator.ofFloat(v, "scaleY", 0.95f); scaleDownX.setDuration(200); scaleDownY.setDuration(200); AnimatorSet scaleDown = new AnimatorSet(); scaleDown.play(scaleDownX).with(scaleDownY); scaleDown.setInterpolator(new OvershootInterpolator()); scaleDown.start(); if (--settingCountDown <= 0) { startSettingActivity(); settingCountDown = 5; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: ObjectAnimator scaleDownX2 = ObjectAnimator.ofFloat(v, "scaleX", 1f); ObjectAnimator scaleDownY2 = ObjectAnimator.ofFloat(v, "scaleY", 1f); scaleDownX2.setDuration(200); scaleDownY2.setDuration(200); AnimatorSet scaleDown2 = new AnimatorSet(); scaleDown2.play(scaleDownX2).with(scaleDownY2); scaleDown2.setInterpolator(new OvershootInterpolator()); scaleDown2.start(); break; } return true; } }); mProgressBar = findViewById(R.id.progressBar); mIpAddressView = findViewById(R.id.text_ipaddress); mPasswordView = findViewById(R.id.text_password); mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { if (id == R.id.button_connect || id == EditorInfo.IME_NULL || id == EditorInfo.IME_ACTION_DONE) { attemptLogin(); } return false; } }); mConnectButton = findViewById(R.id.button_connect); mConnectButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { attemptLogin(); } }); mTextProgress = findViewById(R.id.text_progress); } @Override protected void onStart() { super.onStart(); if (mSharedPref == null) { new SharedPreferenceLoadingTask().execute(); } } /** * Attempts to sign in or register the account specified by the login form. * If there are form errors (invalid email, missing fields, etc.), the * errors are presented and no actual login attempt is made. */ private void attemptLogin() { if (mSnackbar != null) { mSnackbar.dismiss(); } if (mSharedPref == null) { Toast.makeText(this, "Please wait awhile before retrying", Toast.LENGTH_SHORT).show(); return; } InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); View currentFocus = getCurrentFocus(); if (currentFocus != null) { inputManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } // Reset errors. mIpAddressView.setError(null); mPasswordView.setError(null); // Store values at the time of the login attempt. String baseURL = mIpAddressView.getText().toString().trim(); final String password = mPasswordView.getText().toString(); if (baseURL.endsWith("/")) { baseURL = baseURL.substring(0, baseURL.length() - 1); mIpAddressView.setText(baseURL); } boolean cancel = false; View focusView = null; // Check for a valid password, if the user entered one. if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) { mPasswordView.setError(getString(R.string.error_invalid_password)); focusView = mPasswordView; cancel = true; } // Check for a valid email address. if (TextUtils.isEmpty(baseURL)) { mIpAddressView.setError(getString(R.string.error_field_required)); focusView = mIpAddressView; cancel = true; } else if (!(baseURL.startsWith("http://") || baseURL.startsWith("https://"))) { mIpAddressView.setError(getString(R.string.error_invalid_baseurl)); focusView = mIpAddressView; cancel = true; } if (cancel) { focusView.requestFocus(); } else { showProgress(true, getString(R.string.progress_connecting)); String host = Uri.parse(baseURL).getHost(); Log.d("YouQi", "baseURL: " + baseURL); Log.d("YouQi", "host: " + host); if (mAuthTask == null) { mAuthTask = new UserLoginTask(baseURL, host, password); mAuthTask.execute((Void) null); } } } private class UserLoginTask extends AsyncTask<Void, String, ErrorMessage> { private final String mUri; private final String mIpAddress; private final String mPassword; private String mBoostrapData; UserLoginTask(String uri, String ipAddress, String password) { mUri = uri; mIpAddress = ipAddress; mPassword = password; mIpAddressView.setEnabled(false); mPasswordView.setEnabled(false); mConnectButton.setEnabled(false); mProgressBar.setVisibility(View.VISIBLE); mTextProgress.setVisibility(View.VISIBLE); } @Override protected ErrorMessage doInBackground(Void... params) { try { publishProgress(getString(R.string.progress_connecting)); //Response<BootstrapResponse> response = ServiceProvider.getApiService(mUri).bootstrap(mPassword).execute(); Response<String> response = ServiceProvider.getRawApiService(mUri).rawStates(mPassword).execute(); if (response.code() != 200) { if (response.code() == 401) { return new ErrorMessage("Error 401", getString(R.string.error_invalid_password)); } if (response.code() == 404) { return new ErrorMessage("Error 404", getString(R.string.error_invalid_ha_server)); } //OAuthToken token = new Gson().fromJson(response.errorBody().string(), OAuthToken.class); return new ErrorMessage("Error" + response.code(), response.message()); } mBoostrapData = response.body(); final ArrayList<Entity> bootstrapResponse = CommonUtil.inflate(mBoostrapData, new TypeToken<ArrayList<Entity>>() { }.getType()); //final BootstrapResponse bootstrapResponse = CommonUtil.inflate(CommonUtil.readFromAssets(ConnectActivity.this, "bootstrap.txt"), BootstrapResponse.class); //final BootstrapResponse bootstrapResponse = response.body(); CommonUtil.logLargeString("YouQi", "bootstrapResponse: " + bootstrapResponse); publishProgress(getString(R.string.progress_bootstrapping)); SharedPreferences.Editor editor = mSharedPref.edit(); editor.putString(EXTRA_FULL_URI, mUri); editor.putString(EXTRA_IPADDRESS, mIpAddress); editor.putString(EXTRA_PASSWORD, mPassword); editor.putInt("connectionIndex", 0); editor.putLong(EXTRA_LAST_REQUEST, System.currentTimeMillis()).apply(); editor.apply(); DatabaseManager databaseManager = DatabaseManager.getInstance(ConnectActivity.this); databaseManager.updateTables(bootstrapResponse); databaseManager.addConnection(new HomeAssistantServer(mUri, mPassword)); // ArrayList<Entity> entities = databaseManager.getEntities(); // for (Entity entity : entities) { // Log.d("YouQi", "Entity: " + entity.entityId); // } //Crashlytics.setUserIdentifier(settings.bootstrapResponse.profile.loginId); } catch (JsonSyntaxException e) { e.printStackTrace(); return new ErrorMessage("JsonSyntaxException", e); } catch (Exception e) { Log.d("YouQi", "ERROR!"); e.printStackTrace(); Crashlytics.logException(e); return new ErrorMessage(e.getMessage(), e.toString()); } return null; } @Override protected void onProgressUpdate(String... values) { super.onProgressUpdate(values); mConnectButton.setText(values[0]); } @Override protected void onPostExecute(final ErrorMessage errorMessage) { mAuthTask = null; if (errorMessage == null) { mConnectButton.setText(R.string.progress_starting); startMainActivity(); } else { mIpAddressView.setEnabled(true); mPasswordView.setEnabled(true); mConnectButton.setEnabled(true); mConnectButton.setText(R.string.button_connect); mProgressBar.setVisibility(View.GONE); mTextProgress.setVisibility(View.GONE); mPasswordView.requestFocus(); showError(errorMessage.message); if (errorMessage.throwable != null) { sendEmail(mBoostrapData, errorMessage.throwable); } } } @Override protected void onCancelled() { mAuthTask = null; //showProgress(false); } } private void showError(String message) { Drawable warningIcon = ResourcesCompat.getDrawable(getResources(), R.drawable.ic_warning_white_18dp, null); SpannableStringBuilder builder = new SpannableStringBuilder(); builder.append(message); mSnackbar = Snackbar.make(findViewById(android.R.id.content), builder, Snackbar.LENGTH_LONG) .setAction(getString(R.string.action_retry), new OnClickListener() { @Override public void onClick(View view) { attemptLogin(); } }); TextView textView = mSnackbar.getView().findViewById(android.support.design.R.id.snackbar_text); textView.setCompoundDrawablesWithIntrinsicBounds(warningIcon, null, null, null); textView.setCompoundDrawablePadding(getResources().getDimensionPixelOffset(R.dimen.icon_8dp)); mSnackbar.getView().setBackgroundColor(ResourcesCompat.getColor(getResources(), R.color.md_red_A200, null)); mSnackbar.show(); } private boolean isPasswordValid(String password) { return password.length() > 0; } private void showProgress(final boolean show, final String message) { ConnectActivity.this.runOnUiThread(new Runnable() { @Override public void run() { mTextProgress.setVisibility(View.GONE); if (show) { mIpAddressView.setEnabled(false); mPasswordView.setEnabled(false); mConnectButton.setEnabled(false); mConnectButton.setText(message); mProgressBar.setVisibility(View.VISIBLE); mTextProgress.setVisibility(View.VISIBLE); } else { mIpAddressView.setEnabled(true); mPasswordView.setEnabled(true); mConnectButton.setEnabled(true); mConnectButton.setText(R.string.button_connect); mProgressBar.setVisibility(View.GONE); mTextProgress.setVisibility(View.GONE); } } }); } private void startMainActivity() { Intent i = new Intent(ConnectActivity.this, MainActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(new Intent(ConnectActivity.this, MainActivity.class)); overridePendingTransition(R.anim.stay_still, R.anim.fade_out); finish(); } private void startSettingActivity() { Intent i = new Intent(this, SettingsActivity.class); startActivityForResult(i, 2000); } private class SharedPreferenceLoadingTask extends AsyncTask<Void, Void, ErrorMessage> { SharedPreferenceLoadingTask() { showProgress(true, getString(R.string.progress_initializing)); } @Override protected ErrorMessage doInBackground(Void... param) { // if (!CommonUtil.checkSignature(ConnectActivity.this)) { // return new ErrorMessage("Error", getString(R.string.error_corrupted)); // } // // if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) { // return new ErrorMessage("Error", getString(R.string.error_jellybean)); // } PreferenceManager.setDefaultValues(getApplicationContext(), R.xml.preferences, false); mSharedPref = getAppController().getSharedPref(); int ids[] = AppWidgetManager.getInstance(ConnectActivity.this).getAppWidgetIds(new ComponentName(ConnectActivity.this, EntityWidgetProvider.class)); if (ids.length > 0) { ArrayList<String> appWidgetIds = new ArrayList<>(); for (int id : ids) { appWidgetIds.add(Integer.toString(id)); } DatabaseManager databaseManager = DatabaseManager.getInstance(ConnectActivity.this); databaseManager.forceCreate(); databaseManager.housekeepWidgets(appWidgetIds); } //mBundle = getIntent().getExtras(); return null; } @Override protected void onPostExecute(ErrorMessage errorMessage) { if (errorMessage == null) { if (mSharedPref.getString(EXTRA_IPADDRESS, null) != null) { DatabaseManager databaseManager = DatabaseManager.getInstance(ConnectActivity.this); ArrayList<Group> groups = databaseManager.getGroups(); ArrayList<HomeAssistantServer> connections = databaseManager.getConnections(); int dashboardCount = databaseManager.getDashboardCount(); Log.d("YouQi", "dashboardCount: " + dashboardCount); if (groups.size() != 0 && connections.size() != 0 && dashboardCount > 0) { startMainActivity(); return; } } mLayoutMain.setVisibility(View.VISIBLE); mIpAddressView.setText(mSharedPref.getString(EXTRA_FULL_URI, "")); if (mIpAddressView.getText().toString().trim().length() != 0) { mPasswordView.requestFocus(); } else { mIpAddressView.requestFocus(); } showProgress(false, null); } else { mProgressBar.setVisibility(View.GONE); mConnectButton.setVisibility(View.GONE); mTextProgress.setText(errorMessage.message); } super.onPostExecute(errorMessage); } } private void sendEmail(final String content, final Throwable throwable) { final File bootstrapFile = writeToSDFile(content); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); final String sStackTrace = sw.toString(); // stack trace as a string //System.out.println(sStackTrace); new MaterialDialog.Builder(this) .title(R.string.title_send_crash_report) .content(R.string.message_crash_report) .negativeText(getString(R.string.action_dont_send_report)) .positiveText(getString(R.string.action_send_report)) .negativeColorRes(R.color.md_blue_500) .stackingBehavior(StackingBehavior.ADAPTIVE) .positiveColorRes(R.color.md_red_500) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { Intent emailIntent = new Intent(Intent.ACTION_SENDTO); emailIntent.setData(Uri.parse("mailto:")); emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"[email protected]"}); emailIntent.putExtra(Intent.EXTRA_SUBJECT, "HomeAssist Bootstrap Crash Report"); emailIntent.putExtra(Intent.EXTRA_TEXT, sStackTrace); Uri uri = Uri.fromFile(bootstrapFile); emailIntent.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(emailIntent, getString(R.string.title_send_email))); } }) .show(); } private File writeToSDFile(String content) { File rootDir = getExternalCacheDir(); if (rootDir != null) { if (!rootDir.exists()) { boolean isSuccess = rootDir.mkdirs(); if (!isSuccess) { Log.d("YouQi", "failed to create" + rootDir.getAbsolutePath()); } } File bootstrapFile = new File(rootDir, "states.json"); if (bootstrapFile.exists()) { boolean isSuccess = bootstrapFile.delete(); } Log.d("YouQi", "External file system root: " + bootstrapFile.getAbsolutePath()); try { FileOutputStream f = new FileOutputStream(bootstrapFile); PrintWriter pw = new PrintWriter(f); pw.println(content); pw.flush(); pw.close(); f.close(); } catch (Exception e) { e.printStackTrace(); } return bootstrapFile; } return null; } }