login function added, include library Jsoup
This commit is contained in:
parent
b4f565e41f
commit
8f2305303c
@ -22,6 +22,9 @@ dependencies {
|
|||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation 'com.android.support:appcompat-v7:26.1.0'
|
implementation 'com.android.support:appcompat-v7:26.1.0'
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.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'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="tech.goda.studyck">
|
package="tech.goda.studyck">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@ -16,6 +21,9 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".LoginActivity"
|
||||||
|
android:label="@string/title_activity_login"></activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
219
app/src/main/java/tech/goda/studyck/KeyStoreHelper.java
Normal file
219
app/src/main/java/tech/goda/studyck/KeyStoreHelper.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
314
app/src/main/java/tech/goda/studyck/LoginActivity.java
Normal file
314
app/src/main/java/tech/goda/studyck/LoginActivity.java
Normal file
@ -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<Void, Void, Boolean> {
|
||||||
|
|
||||||
|
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<String, String> 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,305 @@
|
|||||||
package tech.goda.studyck;
|
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.support.v7.app.AppCompatActivity;
|
||||||
import android.os.Bundle;
|
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 {
|
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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
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<String, String> 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<String, String> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
381
app/src/main/java/tech/goda/studyck/Network.java
Normal file
381
app/src/main/java/tech/goda/studyck/Network.java
Normal file
@ -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<String, String> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
77
app/src/main/res/layout/activity_login.xml
Normal file
77
app/src/main/res/layout/activity_login.xml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
tools:context="tech.goda.studyck.LoginActivity">
|
||||||
|
|
||||||
|
<!-- Login progress -->
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/login_progress"
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/login_form"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/email_login_form"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/prompt_email"
|
||||||
|
android:inputType="textEmailAddress"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/prompt_password"
|
||||||
|
android:imeActionId="6"
|
||||||
|
android:imeActionLabel="@string/action_sign_in_short"
|
||||||
|
android:imeOptions="actionUnspecified"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/email_sign_in_button"
|
||||||
|
style="?android:textAppearanceSmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/action_sign_in"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/mainActivity"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -7,12 +8,83 @@
|
|||||||
tools:context="tech.goda.studyck.MainActivity">
|
tools:context="tech.goda.studyck.MainActivity">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/messageText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
android:text="Hello World!"
|
android:text="Hello World!"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.29000002" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/uploadButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Upload"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/messageText"
|
||||||
|
app:layout_constraintVertical_bias="1.0" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/choose"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Choose"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/messageText"
|
||||||
|
app:layout_constraintVertical_bias="1.0" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/fileName"
|
||||||
|
android:layout_width="218dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="textPersonName"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/messageText"
|
||||||
|
app:layout_constraintVertical_bias="0.13" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Request Test"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/fileName"
|
||||||
|
app:layout_constraintVertical_bias="0.268" />
|
||||||
|
|
||||||
|
<android.support.constraint.Guideline
|
||||||
|
android:id="@+id/guideline"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.5" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
@ -1,3 +1,17 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">StudyCK</string>
|
<string name="app_name">StudyCK</string>
|
||||||
|
<string name="title_activity_login">Sign in</string>
|
||||||
|
|
||||||
|
<!-- Strings related to login -->
|
||||||
|
<string name="prompt_email">Account</string>
|
||||||
|
<string name="prompt_password">Password</string>
|
||||||
|
<string name="action_sign_in">Sign</string>
|
||||||
|
<string name="action_sign_in_short">Sign in</string>
|
||||||
|
<string name="error_invalid_email">This email address is invalid</string>
|
||||||
|
<string name="error_invalid_password">This password is too short</string>
|
||||||
|
<string name="error_incorrect_password">Account or password is incorrect</string>
|
||||||
|
<string name="error_field_required">This field is required</string>
|
||||||
|
<string name="permission_rationale">"Contacts permissions are needed for providing email
|
||||||
|
completions."
|
||||||
|
</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user