diff --git a/app/build.gradle b/app/build.gradle
index e423523..cacb393 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -22,6 +22,9 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
+ implementation 'com.android.support:design:26.1.0'
+ // jsoup HTML parser library @ https://jsoup.org/
+ compile 'org.jsoup:jsoup:1.11.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c049992..0efebc0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/tech/goda/studyck/KeyStoreHelper.java b/app/src/main/java/tech/goda/studyck/KeyStoreHelper.java
new file mode 100644
index 0000000..655451f
--- /dev/null
+++ b/app/src/main/java/tech/goda/studyck/KeyStoreHelper.java
@@ -0,0 +1,219 @@
+package tech.goda.studyck;
+
+import android.content.Context;
+import android.os.Build;
+import android.security.KeyPairGeneratorSpec;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.support.annotation.RequiresApi;
+import android.util.Base64;
+import android.util.Log;
+
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.util.Calendar;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Created by Jerry on 2018/7/8.
+ */
+
+public class KeyStoreHelper {
+ private static final String TAG = "KEYSTORE";
+
+ private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
+ private static final String AES_MODE = "AES/GCM/NoPadding";
+ private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
+
+ private static final String KEYSTORE_ALIAS = "KEYSTORE_DEMO";
+
+
+ private KeyStore keyStore;
+ private SharedPreferencesHelper prefsHelper;
+
+ public KeyStoreHelper(Context context, SharedPreferencesHelper sharedPreferencesHelper) {
+ try {
+ prefsHelper = sharedPreferencesHelper;
+ keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
+ keyStore.load(null);
+
+ if (!keyStore.containsAlias(KEYSTORE_ALIAS)) {
+ prefsHelper.setIV("");
+ genKeyStoreKey(context);
+ genAESKey();
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+
+ private void genKeyStoreKey(Context context) throws Exception {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ generateRSAKey_AboveApi23();
+ } else {
+ generateRSAKey_BelowApi23(context);
+ }
+ }
+
+
+
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ private void generateRSAKey_AboveApi23() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator
+ .getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER);
+
+
+ KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec
+ .Builder(KEYSTORE_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
+ .build();
+
+ keyPairGenerator.initialize(keyGenParameterSpec);
+ keyPairGenerator.generateKeyPair();
+
+ }
+
+ private void generateRSAKey_BelowApi23(Context context) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
+ Calendar start = Calendar.getInstance();
+ Calendar end = Calendar.getInstance();
+ end.add(Calendar.YEAR, 30);
+
+ KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
+ .setAlias(KEYSTORE_ALIAS)
+ .setSubject(new X500Principal("CN=" + KEYSTORE_ALIAS))
+ .setSerialNumber(BigInteger.TEN)
+ .setStartDate(start.getTime())
+ .setEndDate(end.getTime())
+ .build();
+
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator
+ .getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER);
+
+ keyPairGenerator.initialize(spec);
+ keyPairGenerator.generateKeyPair();
+ }
+
+ public String encrypt(String plainText) {
+ try {
+ return encryptAES(plainText);
+
+ } catch (Exception e) {
+ Log.d(TAG, Log.getStackTraceString(e));
+ return "";
+ }
+ }
+ public String decrypt(String encryptedText) {
+ try {
+ return decryptAES(encryptedText);
+
+ } catch (Exception e) {
+ Log.d(TAG, Log.getStackTraceString(e));
+ return "";
+ }
+
+ }
+
+
+ private String encryptRSA(byte[] plainText) throws Exception {
+ PublicKey publicKey = keyStore.getCertificate(KEYSTORE_ALIAS).getPublicKey();
+
+ Cipher cipher = Cipher.getInstance(RSA_MODE);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+
+ byte[] encryptedByte = cipher.doFinal(plainText);
+ return Base64.encodeToString(encryptedByte, Base64.DEFAULT);
+ }
+
+
+ private byte[] decryptRSA(String encryptedText) throws Exception {
+ PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEYSTORE_ALIAS, null);
+
+ Cipher cipher = Cipher.getInstance(RSA_MODE);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+ byte[] encryptedBytes = Base64.decode(encryptedText, Base64.DEFAULT);
+ byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
+
+ return decryptedBytes;
+ }
+
+ private void genAESKey() throws Exception {
+ // Generate AES-Key
+ byte[] aesKey = new byte[16];
+ SecureRandom secureRandom = new SecureRandom();
+ secureRandom.nextBytes(aesKey);
+
+
+ // Generate 12 bytes iv then save to SharedPrefs
+ byte[] generated = secureRandom.generateSeed(12);
+ String iv = Base64.encodeToString(generated, Base64.DEFAULT);
+ prefsHelper.setIV(iv);
+
+
+ // Encrypt AES-Key with RSA Public Key then save to SharedPrefs
+ String encryptAESKey = encryptRSA(aesKey);
+ prefsHelper.setAESKey(encryptAESKey);
+ }
+
+
+ /**
+ * AES Encryption
+ * @param plainText: A string which needs to be encrypted.
+ * @return A base64's string after encrypting.
+ */
+ private String encryptAES(String plainText) throws Exception {
+ Cipher cipher = Cipher.getInstance(AES_MODE);
+ cipher.init(Cipher.ENCRYPT_MODE, getAESKey(), new IvParameterSpec(getIV()));
+
+ // 加密過後的byte
+ byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
+
+ // 將byte轉為base64的string編碼
+ return Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
+ }
+
+
+ private String decryptAES(String encryptedText) throws Exception {
+ // 將加密過後的Base64編碼格式 解碼成 byte
+ byte[] decodedBytes = Base64.decode(encryptedText.getBytes(), Base64.DEFAULT);
+
+ // 將解碼過後的byte 使用AES解密
+ Cipher cipher = Cipher.getInstance(AES_MODE);
+ cipher.init(Cipher.DECRYPT_MODE, getAESKey(), new IvParameterSpec(getIV()));
+
+ return new String(cipher.doFinal(decodedBytes));
+ }
+
+
+ private byte[] getIV() {
+ String prefIV = prefsHelper.getIV();
+ return Base64.decode(prefIV, Base64.DEFAULT);
+ }
+
+
+
+ private SecretKeySpec getAESKey() throws Exception {
+ String encryptedKey = prefsHelper.getAESKey();
+ byte[] aesKey = decryptRSA(encryptedKey);
+
+ return new SecretKeySpec(aesKey, AES_MODE);
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/tech/goda/studyck/LoginActivity.java b/app/src/main/java/tech/goda/studyck/LoginActivity.java
new file mode 100644
index 0000000..68a732e
--- /dev/null
+++ b/app/src/main/java/tech/goda/studyck/LoginActivity.java
@@ -0,0 +1,314 @@
+package tech.goda.studyck;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AppCompatActivity;
+import android.app.LoaderManager.LoaderCallbacks;
+
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.inputmethod.EditorInfo;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static android.Manifest.permission.READ_CONTACTS;
+import static tech.goda.studyck.Network.LOGIN_URI;
+
+/**
+ * A login screen that offers login via email/password.
+ */
+public class LoginActivity extends AppCompatActivity {
+
+ /**
+ * Id to identity READ_CONTACTS permission request.
+ */
+ private static final int REQUEST_READ_CONTACTS = 0;
+
+ /**
+ * A dummy authentication store containing known user names and passwords.
+ * TODO: remove after connecting to a real authentication system.
+ */
+ private static final String[] DUMMY_CREDENTIALS = new String[]{
+ "foo@example.com:hello", "bar@example.com:world"
+ };
+ /**
+ * Keep track of the login task to ensure we can cancel it if requested.
+ */
+ private UserLoginTask mAuthTask = null;
+
+ // UI references.
+ private AutoCompleteTextView mEmailView;
+ private EditText mPasswordView;
+ private View mProgressView;
+ private View mLoginFormView;
+ private String response = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_login);
+ // Set up the login form.
+
+
+
+
+ mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
+
+ mPasswordView = (EditText) findViewById(R.id.password);
+ mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
+ if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
+ attemptLogin();
+ return true;
+ }
+ return false;
+ }
+ });
+
+ Intent intent = this.getIntent();
+
+ try{
+ mEmailView.setText(intent.getExtras().getString("account"));
+ mPasswordView.setText(intent.getExtras().getString("password"));
+ } catch(NullPointerException e){
+ Log.e("Login", Log.getStackTraceString(e));
+ }
+
+ Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
+ mEmailSignInButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ attemptLogin();
+ }
+ });
+
+ mLoginFormView = findViewById(R.id.login_form);
+ mProgressView = findViewById(R.id.login_progress);
+ }
+
+ private boolean mayRequestContacts() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return true;
+ }
+ if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
+ Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
+ .setAction(android.R.string.ok, new View.OnClickListener() {
+ @Override
+ @TargetApi(Build.VERSION_CODES.M)
+ public void onClick(View v) {
+ requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
+ }
+ });
+ } else {
+ requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
+ }
+ return false;
+ }
+
+ /**
+ * Callback received when a permissions request has been completed.
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ if (requestCode == REQUEST_READ_CONTACTS) {
+ if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+
+ }
+ }
+ }
+
+
+ /**
+ * 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 (mAuthTask != null) {
+ return;
+ }
+
+ // Reset errors.
+ mEmailView.setError(null);
+ mPasswordView.setError(null);
+
+ // Store values at the time of the login attempt.
+ String email = mEmailView.getText().toString();
+ String password = mPasswordView.getText().toString();
+
+ 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(email)) {
+ mEmailView.setError(getString(R.string.error_field_required));
+ focusView = mEmailView;
+ cancel = true;
+ } else if (!isEmailValid(email)) {
+ mEmailView.setError(getString(R.string.error_invalid_email));
+ focusView = mEmailView;
+ cancel = true;
+ }
+
+ if (cancel) {
+ // There was an error; don't attempt login and focus the first
+ // form field with an error.
+ focusView.requestFocus();
+ } else {
+ // Show a progress spinner, and kick off a background task to
+ // perform the user login attempt.
+ showProgress(true);
+ mAuthTask = new UserLoginTask(email, password);
+ mAuthTask.execute((Void) null);
+ }
+ }
+
+ private boolean isEmailValid(String email) {
+ //TODO: Replace this with your own logic
+ return true;
+ }
+
+ private boolean isPasswordValid(String password) {
+ //TODO: Replace this with your own logic
+ return true;
+ }
+
+ /**
+ * Shows the progress UI and hides the login form.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
+ private void showProgress(final boolean show) {
+ // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
+ // for very easy animations. If available, use these APIs to fade-in
+ // the progress spinner.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
+ int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
+
+ mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
+ mLoginFormView.animate().setDuration(shortAnimTime).alpha(
+ show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
+ }
+ });
+
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
+ mProgressView.animate().setDuration(shortAnimTime).alpha(
+ show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ });
+ } else {
+ // The ViewPropertyAnimator APIs are not available, so simply show
+ // and hide the relevant UI components.
+ mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
+ mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
+ }
+ }
+
+
+ /**
+ * Represents an asynchronous login/registration task used to authenticate
+ * the user.
+ */
+ public class UserLoginTask extends AsyncTask {
+
+ private final String mEmail;
+ private final String mPassword;
+
+ UserLoginTask(String email, String password) {
+ mEmail = email;
+ mPassword = password;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ // TODO: attempt authentication against a network service.
+
+ // Simulate network access.
+ Map param = new HashMap<>();
+ param.put("f_uid", mEmail);
+ param.put("f_pwd", mPassword);
+ response = Network.requestPost(LOGIN_URI, param);
+ //Thread.sleep(2000);
+ Log.e("Login", response);
+ return !response.contains("錯誤");
+
+ }
+
+ @Override
+ protected void onPostExecute(final Boolean success) {
+ mAuthTask = null;
+ showProgress(false);
+
+ if (success) {
+ Intent intent = new Intent();
+ intent.putExtra("response", response);
+ intent.putExtra("account", mEmail);
+ intent.putExtra("password", mPassword);
+ setResult(RESULT_OK, intent);
+ finish();
+ } else {
+ mPasswordView.setError(getString(R.string.error_incorrect_password));
+ mPasswordView.requestFocus();
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ mAuthTask = null;
+ showProgress(false);
+ }
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if ((keyCode == KeyEvent.KEYCODE_BACK)) {
+ //Toast.makeText(getApplicationContext(), "請登入SAD", Toast.LENGTH_SHORT).show();
+ finishAffinity();
+ }
+ return super.onKeyDown(keyCode, event);
+
+ }
+}
+
diff --git a/app/src/main/java/tech/goda/studyck/MainActivity.java b/app/src/main/java/tech/goda/studyck/MainActivity.java
index 1f99ade..fad6140 100644
--- a/app/src/main/java/tech/goda/studyck/MainActivity.java
+++ b/app/src/main/java/tech/goda/studyck/MainActivity.java
@@ -1,13 +1,305 @@
package tech.goda.studyck;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.support.annotation.NonNull;
+import android.support.v4.provider.DocumentFile;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
+import android.text.Html;
+import android.text.Layout;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.select.Elements;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.KeyStore;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
public class MainActivity extends AppCompatActivity {
+ private static final int PICK_FILE_REQUEST = 0;
+ private static final int LOGIN = 1;
+ TextView messageText;
+ Button uploadButton, choose;
+ EditText editFileName;
+ int serverResponseCode = 0;
+
+ String uploadServerUri = null;
+ InputStream in;
+ String fileName;
+ String loginResponse;
+ /********** File Path *************/
+ String uploadFilePath = Environment.getExternalStorageDirectory().getPath() + "/test.png";
+ Button button3;
+
+ KeyStoreHelper keyStoreHelper;
+ SharedPreferencesHelper preferencesHelper;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ Log.e("Directory", uploadFilePath);
+ uploadButton = findViewById(R.id.uploadButton);
+ choose = findViewById(R.id.choose);
+ messageText = findViewById(R.id.messageText);
+ editFileName = findViewById(R.id.fileName);
+ button3 = findViewById(R.id.button3);
+
+ CookieManager manager = new CookieManager();
+ CookieHandler.setDefault(manager);
+
+ preferencesHelper = new SharedPreferencesHelper(getApplicationContext());
+ keyStoreHelper = new KeyStoreHelper(getApplicationContext(), preferencesHelper);
+
+ messageText.setText("Uploading file path : " + uploadFilePath);
+
+
+ //uploadServerUri = "http://study.ck.tp.edu.tw/login_chk.asp";
+ uploadServerUri = "http://192.168.173.104/WebPageTest/upload-big5.php";
+
+ uploadButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+
+ new Thread(new Runnable() {
+ public void run() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ messageText.setText("uploading started.....");
+ }
+ });
+
+ Network.uploadFile(uploadServerUri, in, editFileName.getText().toString());
+
+ }
+ }).start();
+ }
+ });
+
+ button3.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Map params = new HashMap<>();
+
+ params.put("f_mnuid", "");
+
+ new Thread(new Runnable() {
+ public void run() {
+ Network.requestPost("http://study.ck.tp.edu.tw", params);
+ }
+ }).start();
+ }
+ });
+
+ choose.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showFileChooser();
+ }
+ });
+
+ String encryptedText = preferencesHelper.getInput();
+ final String mEmail = preferencesHelper.getString(SharedPreferencesHelper.PREF_AC);
+ final String mPassword = keyStoreHelper.decrypt(encryptedText);
+ if(mEmail.equals("") || mPassword.equals("")){
+ callLogin(mEmail, mPassword);
+ }
+ else{
+ final Map param = new HashMap<>();
+ param.put("f_uid", mEmail);
+ param.put("f_pwd", mPassword);
+ Toast.makeText(getApplicationContext(), "自動登入中...", Toast.LENGTH_SHORT).show();
+
+ View layout = findViewById(android.R.id.content);
+ layout.setVisibility(View.GONE);
+
+ new Thread(new Runnable() {
+ public void run() {
+ loginResponse = Network.requestPost(Network.LOGIN_URI, param);
+ //Thread.sleep(2000);
+ Log.e("Login", loginResponse);
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if(!loginResponse.contains("錯誤")){
+ LoginSuccess(mEmail, mPassword);
+ }
+ else{
+ Toast.makeText(getApplicationContext(), "Login Failed", Toast.LENGTH_SHORT).show();
+ callLogin(mEmail, mPassword);
+ }
+ }
+ });
+ }
+ }).start();
+
+
+ //return !response.contains("錯誤");
+ }
+
}
+
+ private void callLogin(String account, String password) {
+ Intent intent = new Intent(MainActivity.this, LoginActivity.class);
+ intent.putExtra("account", account);
+ intent.putExtra("password", password);
+ startActivityForResult(intent, LOGIN);
+ }
+
+
+ private void LoginSuccess(String account, String password) {
+ View layout = findViewById(android.R.id.content);
+ Toast.makeText(getApplicationContext(), "登入成功!!", Toast.LENGTH_SHORT).show();
+ Document doc = Jsoup.parse(loginResponse);
+ String name = doc.select("form > font").first().text();
+ messageText.setText(name);
+
+ // Save Login Information
+ String encryptedPassword = keyStoreHelper.encrypt(password);
+ preferencesHelper.setInput(encryptedPassword);
+ preferencesHelper.putString(SharedPreferencesHelper.PREF_AC, account);
+
+ layout.setVisibility(View.VISIBLE);
+
+ }
+
+ private void showFileChooser() {
+ Intent intent = new Intent();
+ //sets the select file to all types of files
+ intent.setType("*/*");
+ //allows to select data and return it
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ //starts new activity to select file and return data
+ startActivityForResult(Intent.createChooser(intent,"Choose File to Upload.."), PICK_FILE_REQUEST);
+ }
+
+ private boolean isVirtualFile(Uri uri) {
+ if (!DocumentsContract.isDocumentUri(this, uri)) {
+ return false;
+ }
+
+ Cursor cursor = getContentResolver().query(
+ uri,
+ new String[] { DocumentsContract.Document.COLUMN_FLAGS },
+ null, null, null);
+
+ int flags = 0;
+ if (cursor.moveToFirst()) {
+ flags = cursor.getInt(0);
+ }
+ cursor.close();
+
+ return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
+ }
+
+ private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter)
+ throws IOException {
+
+ ContentResolver resolver = getContentResolver();
+
+ String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);
+
+ if (openableMimeTypes == null ||
+ openableMimeTypes.length < 1) {
+ throw new FileNotFoundException();
+ }
+
+ return resolver
+ .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
+ .createInputStream();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ switch (requestCode){
+ case PICK_FILE_REQUEST:
+ if(resultCode == RESULT_OK) {
+ if (data == null) {
+ //no data present
+ return;
+ }
+
+
+ Uri selectedFileUri = data.getData();
+ fileName = DocumentFile.fromSingleUri(this, selectedFileUri).getName();
+ editFileName.setText(fileName);
+ messageText.setText("Upload File:" + fileName + "(Change file name below)");
+ Log.e("GetPathDocumentName", fileName);
+
+ try {
+ if (isVirtualFile(selectedFileUri)) {
+ Log.e("GetPath", "This is virtual file");
+ in = getInputStreamForVirtualFile(selectedFileUri, "*/*");
+
+ } else {
+ in = getContentResolver().openInputStream(selectedFileUri);
+ }
+
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e("GetPathError", e.toString());
+ }
+ }
+ /*if(uploadFilePath != null && !uploadFilePath.equals("")){
+ messageText.setText(uploadFilePath);
+ }else{
+ Toast.makeText(this,"Cannot upload file to server",Toast.LENGTH_SHORT).show();
+ }*/
+ break;
+ case LOGIN:
+ if(resultCode == RESULT_OK){
+ Bundle bundle = data.getExtras();
+ String account = bundle.getString("account");
+ String password = bundle.getString("password");
+ loginResponse = bundle.getString("response");
+ //messageText.setText(loginResponse);
+ LoginSuccess(account, password);
+ }
+ break;
+ }
+
+
+ }
+
+
+
}
diff --git a/app/src/main/java/tech/goda/studyck/Network.java b/app/src/main/java/tech/goda/studyck/Network.java
new file mode 100644
index 0000000..c9be78d
--- /dev/null
+++ b/app/src/main/java/tech/goda/studyck/Network.java
@@ -0,0 +1,381 @@
+package tech.goda.studyck;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.support.v4.provider.DocumentFile;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Created by Jerry on 2018/7/7.
+ */
+
+public class Network {
+ public static final String LOGIN_URI = "http://study.ck.tp.edu.tw/login_chk.asp";
+
+ public static String uploadFile(String sourceFileUri, InputStream in, String uploadFileName) {
+
+
+ //String fileName = sourceFileUri;
+ HttpURLConnection conn = null;
+ //CookieManager cookieManager = null;
+ DataOutputStream dos = null;
+ String lineEnd = "\r\n";
+ String twoHyphens = "--";
+ String boundary = "*****";
+ int bytesRead, bytesAvailable, bufferSize;
+ byte[] buffer;
+ int maxBufferSize = 1 * 1024 * 1024;
+
+ //File sourceFile = new File(sourceFileUri);
+ String response = null;
+ if(in == null){
+ /*runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(MainActivity.this, "Please Choose File First.", Toast.LENGTH_SHORT).show();
+ }
+ });*/
+ return null;
+ }
+ else{
+ try {
+
+ // open a URL connection to the Servlet
+ //FileInputStream fileInputStream = new FileInputStream(sourceFile);
+ URL url = new URL(sourceFileUri);
+
+ // Open a HTTP connection to the URL
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setDoInput(true); // Allow Inputs
+ conn.setDoOutput(true); // Allow Outputs
+ conn.setUseCaches(false); // Don't use a Cached Copy
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Connection", "Keep-Alive");
+
+ conn.setRequestProperty("ENCTYPE", "multipart/form-data");
+ conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
+ conn.setRequestProperty("f_file", uploadFileName);
+
+ dos = new DataOutputStream(conn.getOutputStream());
+
+ dos.writeBytes(twoHyphens + boundary + lineEnd);
+ dos.writeBytes("Content-Disposition: form-data; name=\"f_hwintro\""+ lineEnd + lineEnd);
+ dos.writeBytes("" + lineEnd);
+ dos.writeBytes(twoHyphens + boundary + lineEnd);
+ dos.writeBytes("Content-Disposition: form-data; name=\"f_file\";filename=\"");
+ dos.write(uploadFileName.getBytes());
+ dos.writeBytes("\"" + lineEnd);
+
+ dos.writeBytes(lineEnd);
+
+ // create a buffer of maximum size
+ bytesAvailable = in.available();
+
+ bufferSize = Math.min(bytesAvailable, maxBufferSize);
+ buffer = new byte[bufferSize];
+
+ // read file and write it into form...
+ bytesRead = in.read(buffer, 0, bufferSize);
+ while (bytesRead > 0) {
+
+ dos.write(buffer, 0, bufferSize);
+ bytesAvailable = in.available();
+ bufferSize = Math.min(bytesAvailable, maxBufferSize);
+ bytesRead = in.read(buffer, 0, bufferSize);
+
+ }
+
+ // send multipart form data necessary after file data...
+ dos.writeBytes(lineEnd);
+ dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
+
+ // Responses from the server (code and message)
+ int serverResponseCode = conn.getResponseCode();
+ String serverResponseMessage = conn.getResponseMessage();
+ try{
+ BufferedReader br;
+ if (200 <= serverResponseCode && serverResponseCode <= 299) {
+ br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "big5"));
+ } else {
+ br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "big5"));
+ }
+ StringBuilder sb = new StringBuilder();
+ String output;
+ while ((output = br.readLine()) != null) {
+ sb.append(output);
+ }
+
+ response = sb.toString();
+ Log.e("Response", response);
+ } catch(java.io.IOException e){
+ e.printStackTrace();
+ }
+ Log.e("uploadFile", "HTTP Response is : "
+ + serverResponseMessage + ": " + serverResponseCode);
+
+ if(serverResponseCode == 200){
+
+ /*runOnUiThread(new Runnable() {
+ public void run() {
+
+ String msg = "File Upload Completed.";
+
+ messageText.setText(msg);
+ //Toast.makeText(MainActivity.this, "File Upload Completed.",
+ //Toast.LENGTH_SHORT).show();
+ }
+ });*/
+ }
+
+ //close the streams //
+ in.close();
+ dos.flush();
+ dos.close();
+
+ } catch (MalformedURLException e) {
+
+ e.printStackTrace();
+ Log.e("Upload file to server", "error: " + e.getMessage(), e);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e("Upload", "Exception : "
+ + e.getMessage(), e);
+ }
+ return response;
+ }
+ /*if (!sourceFile.isFile()) {
+
+ Log.e("uploadFile", "Source File not exist :"
+ +uploadFilePath);
+
+ return 0;
+
+ }
+
+ else
+ {
+ try {
+
+ // open a URL connection to the Servlet
+ FileInputStream fileInputStream = new FileInputStream(sourceFile);
+ URL url = new URL(uploadServerUri);
+
+ // Open a HTTP connection to the URL
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setDoInput(true); // Allow Inputs
+ conn.setDoOutput(true); // Allow Outputs
+ conn.setUseCaches(false); // Don't use a Cached Copy
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Connection", "Keep-Alive");
+ conn.setRequestProperty("ENCTYPE", "multipart/form-data");
+ conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
+ conn.setRequestProperty("f_file", fileName);
+
+ dos = new DataOutputStream(conn.getOutputStream());
+
+ dos.writeBytes(twoHyphens + boundary + lineEnd);
+ dos.writeBytes("Content-Disposition: form-data; name=\"f_hwintro\""+ lineEnd + lineEnd);
+ dos.writeBytes("" + lineEnd);
+ dos.writeBytes(twoHyphens + boundary + lineEnd);
+ dos.writeBytes("Content-Disposition: form-data; name=\"f_file\";filename=\""
+ + fileName + "\"" + lineEnd);
+
+ dos.writeBytes(lineEnd);
+
+ // create a buffer of maximum size
+ bytesAvailable = fileInputStream.available();
+
+ bufferSize = Math.min(bytesAvailable, maxBufferSize);
+ buffer = new byte[bufferSize];
+
+ // read file and write it into form...
+ bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+ while (bytesRead > 0) {
+
+ dos.write(buffer, 0, bufferSize);
+ bytesAvailable = fileInputStream.available();
+ bufferSize = Math.min(bytesAvailable, maxBufferSize);
+ bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+
+ }
+
+ // send multipart form data necesssary after file data...
+ dos.writeBytes(lineEnd);
+ dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
+
+
+
+ // Responses from the server (code and message)
+ serverResponseCode = conn.getResponseCode();
+ String serverResponseMessage = conn.getResponseMessage();
+ try{
+ BufferedReader br = null;
+ if (200 <= serverResponseCode && serverResponseCode <= 299) {
+ br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ } else {
+ br = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
+ }
+ StringBuilder sb = new StringBuilder();
+ String output;
+ while ((output = br.readLine()) != null) {
+ sb.append(output);
+ }
+
+ Log.e("Response", sb.toString());
+ } catch(java.io.IOException e){
+ e.printStackTrace();
+ }
+ Log.e("uploadFile", "HTTP Response is : "
+ + serverResponseMessage + ": " + serverResponseCode);
+
+ if(serverResponseCode == 200){
+
+ runOnUiThread(new Runnable() {
+ public void run() {
+
+ String msg = "File Upload Completed.";
+
+ messageText.setText(msg);
+ Toast.makeText(MainActivity.this, "File Upload Complete.",
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ //close the streams //
+ fileInputStream.close();
+ dos.flush();
+ dos.close();
+
+ } catch (MalformedURLException e) {
+
+ e.printStackTrace();
+
+
+
+ Log.e("Upload file to server", "error: " + e.getMessage(), e);
+ } catch (Exception e) {
+
+ e.printStackTrace();
+
+
+ Log.e("Upload", "Exception : "
+ + e.getMessage(), e);
+ }
+ return serverResponseCode;
+
+ } // End else block
+ */
+ }
+
+
+
+ public static String requestPost(String uri, Map params){
+ HttpURLConnection conn;
+ DataOutputStream dos;
+ String response = null;
+ try {
+ // open a URL connection to the Servlet
+ URL url = new URL(uri);
+ // Open a HTTP connection to the URL
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setDoInput(true); // Allow Inputs
+ conn.setDoOutput(true); // Allow Outputs
+ conn.setUseCaches(false); // Don't use a Cached Copy
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Connection", "Keep-Alive");
+ dos = new DataOutputStream(conn.getOutputStream());
+
+ Set keySet = params.keySet();
+ StringBuilder sb = new StringBuilder();
+ for (Object objKey : keySet) {
+ //有了鍵就可以通過map集合的get方法獲取其對應的値
+
+ String key = objKey.toString();
+ String value = params.get(key);
+
+ sb.append(key).append("=").append(value).append("&");
+
+ Log.e("Params", "key: " + key + ", value: " + value);
+ }
+ if(params.size() != 0){
+ sb.deleteCharAt(sb.length()-1);
+ }
+
+ Log.e("StringBuilder", sb.toString());
+ dos.writeBytes(sb.toString());
+
+
+ // Responses from the server (code and message)
+ int serverResponseCode = conn.getResponseCode();
+ String serverResponseMessage = conn.getResponseMessage();
+ try{
+ BufferedReader br;
+ if (200 <= serverResponseCode && serverResponseCode <= 299) {
+ br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "big5"));
+ } else {
+ br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "UTF-8"));
+ }
+ sb = new StringBuilder();
+ String output;
+ while ((output = br.readLine()) != null) {
+ sb.append(output);
+ }
+ response = sb.toString();
+ Log.e("Response", response);
+ } catch(java.io.IOException e){
+ e.printStackTrace();
+ }
+ Log.e("uploadFile", "HTTP Response is : "
+ + serverResponseMessage + ": " + serverResponseCode);
+
+ if(serverResponseCode == 200){
+
+ /*runOnUiThread(new Runnable() {
+ public void run() {
+
+ String msg = "File Upload Completed.";
+
+ messageText.setText(msg);
+ //Toast.makeText(MainActivity.this, "File Upload Completed.",
+ //Toast.LENGTH_SHORT).show();
+ }
+ });*/
+ }
+
+ dos.flush();
+ dos.close();
+
+ } catch (MalformedURLException e) {
+
+ e.printStackTrace();
+ Log.e("Upload file to server", "error: " + e.getMessage(), e);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e("Upload", "Exception : "
+ + e.getMessage(), e);
+ }
+
+ return response;
+ }
+
+
+
+}
diff --git a/app/src/main/java/tech/goda/studyck/SharedPreferencesHelper.java b/app/src/main/java/tech/goda/studyck/SharedPreferencesHelper.java
new file mode 100644
index 0000000..8cc6431
--- /dev/null
+++ b/app/src/main/java/tech/goda/studyck/SharedPreferencesHelper.java
@@ -0,0 +1,76 @@
+package tech.goda.studyck;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Created by Jerry on 2018/7/8.
+ */
+
+
+public class SharedPreferencesHelper {
+
+ private static final String SHARED_PREF_NAME = "KEYSTORE_SETTING";
+
+ private static final String PREF_KEY_AES = "PREF_KEY_AES";
+ private static final String PREF_KEY_IV = "PREF_KEY_IV";
+ private static final String PREF_KEY_INPUT = "PREF_KEY_INPUT";
+ public static final String PREF_AC = "PREF_AC";
+
+ private SharedPreferences sharedPreferences;
+
+
+
+ SharedPreferencesHelper(Context context){
+ sharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
+ }
+
+
+ String getString(String key) {
+ return sharedPreferences.getString(key, "");
+ }
+
+ void putString(String key, String value) {
+ sharedPreferences.edit()
+ .putString(key, value)
+ .apply();
+ }
+
+ private boolean getBoolean(String key) {
+ return sharedPreferences.getBoolean(key, false);
+ }
+
+ private void putBoolean(String key, boolean value) {
+ sharedPreferences.edit()
+ .putBoolean(key, value)
+ .apply();
+ }
+
+
+
+
+ void setIV(String value) {
+ putString(PREF_KEY_IV, value);
+ }
+
+ String getIV() {
+ return getString(PREF_KEY_IV);
+ }
+
+ void setAESKey(String value) {
+ putString(PREF_KEY_AES, value);
+ }
+
+ String getAESKey() {
+ return getString(PREF_KEY_AES);
+ }
+
+ public void setInput(String value) {
+ putString(PREF_KEY_INPUT, value);
+ }
+
+ String getInput() {
+ return getString(PREF_KEY_INPUT);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..1388501
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 9ad3a7a..49eb4e1 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,5 +1,6 @@
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.29000002" />
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b144911..f5dfc63 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,17 @@
StudyCK
+ Sign in
+
+
+ Account
+ Password
+ Sign
+ Sign in
+ This email address is invalid
+ This password is too short
+ Account or password is incorrect
+ This field is required
+ "Contacts permissions are needed for providing email
+ completions."
+