├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── dictionaries
│ └── chris.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
├── vcs.xml
└── workspace.xml
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── org
│ │ └── cry
│ │ └── otp
│ │ ├── DBAdapter.java
│ │ ├── HOTP.java
│ │ ├── Home.java
│ │ ├── MD5.java
│ │ ├── ProfileSetup.java
│ │ ├── Profiles.java
│ │ ├── TOTP.java
│ │ └── mOTP.java
│ ├── res
│ ├── drawable
│ │ ├── ic_menu_about.png
│ │ ├── ic_menu_login.png
│ │ ├── ic_menu_time.png
│ │ └── icon.png
│ ├── layout
│ │ ├── hotp_main.xml
│ │ ├── hotp_setup.xml
│ │ ├── motp_main.xml
│ │ ├── motp_setup.xml
│ │ ├── profile_list_item.xml
│ │ ├── profiles.xml
│ │ ├── spinner_layout.xml
│ │ ├── totp_main.xml
│ │ └── totp_setup.xml
│ ├── values-zh-rCN
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-zh-rTW
│ │ ├── arrays.xml
│ │ └── strings.xml
│ └── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── resources
│ └── org
│ └── cry
│ └── otp
│ └── .idea
│ ├── .name
│ ├── compiler.xml
│ ├── copyright
│ └── profiles_settings.xml
│ ├── encodings.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── otp.iml
│ ├── vcs.xml
│ └── workspace.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/dictionaries/chris.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | hmac
5 | hotp
6 | miceli
7 | motp
8 | openauthentication
9 | totp
10 | uncalculated
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | 1609468447247
187 |
188 |
189 | 1609468447247
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | compileSdk 34
7 |
8 | defaultConfig {
9 | applicationId "org.cry.otp"
10 | minSdkVersion 21
11 | targetSdkVersion 34
12 | versionCode 26
13 | versionName "4"
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_8
24 | targetCompatibility JavaVersion.VERSION_1_8
25 | }
26 | namespace 'org.cry.otp'
27 | }
28 |
29 | dependencies {
30 | implementation 'androidx.appcompat:appcompat:1.7.0'
31 | implementation 'com.google.android.material:material:1.12.0'
32 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/org/cry/otp/DBAdapter.java:
--------------------------------------------------------------------------------
1 | package org.cry.otp;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.database.SQLException;
7 | import android.database.sqlite.SQLiteDatabase;
8 | import android.database.sqlite.SQLiteOpenHelper;
9 | import android.util.Log;
10 |
11 | class DBAdapter {
12 | public static final String KEY_ROW_ID = "_id";
13 | public static final String KEY_PROF_NAME = "prof_name";
14 | public static final String KEY_SEED = "seed";
15 | public static final String KEY_OTP_TYPE = "otp_type";
16 | public static final String KEY_COUNT = "count";
17 | public static final String KEY_TIME_ZONE = "time_zone";
18 | public static final String KEY_DIGITS = "digits";
19 | public static final String KEY_TIME_INTERVAL = "time_interval";
20 | private static final String TAG = "DBAdapter";
21 | private static final String DATABASE_NAME = "profs";
22 | private static final String DATABASE_TABLE = "profiles";
23 | private static final int DATABASE_VERSION = 4;
24 | private static final String DATABASE_CREATE = "create table "
25 | + DATABASE_TABLE + " (" + KEY_ROW_ID
26 | + " integer primary key autoincrement, " + KEY_PROF_NAME
27 | + " text not null, " + KEY_SEED + " text not null, " + KEY_OTP_TYPE
28 | + " integer not null, " + KEY_COUNT + " integer not null, "
29 | + KEY_DIGITS + " integer not null, " + KEY_TIME_ZONE
30 | + " text not null, " + KEY_TIME_INTERVAL + " integer not null);";
31 | private final DatabaseHelper DBHelper;
32 | private SQLiteDatabase db;
33 |
34 | public DBAdapter(Context ctx) {
35 | DBHelper = new DatabaseHelper(ctx);
36 | }
37 |
38 | public void reCreate() {
39 | db.execSQL("DROP TABLE IF EXISTS profiles");
40 | db.execSQL(DATABASE_CREATE);
41 | }
42 |
43 | // ---opens the database---
44 | public void open() throws SQLException {
45 | db = DBHelper.getWritableDatabase();
46 | }
47 |
48 | // ---closes the database---
49 | public void close() {
50 | DBHelper.close();
51 | }
52 |
53 | // ---insert a title into the database---
54 | public long insertProfile(String prof_name, String seed, int otpType,
55 | int digits, String time_zone, int timeInterval) {
56 | ContentValues initialValues = new ContentValues();
57 | initialValues.put(KEY_PROF_NAME, prof_name);
58 | initialValues.put(KEY_SEED, seed);
59 | initialValues.put(KEY_OTP_TYPE, otpType);
60 | initialValues.put(KEY_COUNT, 0);
61 | initialValues.put(KEY_DIGITS, digits);
62 | initialValues.put(KEY_TIME_ZONE, time_zone);
63 | initialValues.put(KEY_TIME_INTERVAL, timeInterval);
64 | return db.insert(DATABASE_TABLE, null, initialValues);
65 | }
66 |
67 | // ---deletes a particular title---
68 | public void deleteProfile(int rowId) {
69 | db.delete(DATABASE_TABLE, KEY_ROW_ID + "=" + rowId, null);
70 | }
71 |
72 | public Cursor getAllProfiles() {
73 | return db.query(DATABASE_TABLE, new String[]{KEY_ROW_ID,
74 | KEY_PROF_NAME, KEY_SEED, KEY_OTP_TYPE, KEY_COUNT, KEY_DIGITS,
75 | KEY_TIME_ZONE, KEY_TIME_INTERVAL}, null, null, null, null,
76 | null);
77 | }
78 |
79 | // ---retrieves a particular title---
80 | public Cursor getProfile(int rowId) throws SQLException {
81 | Cursor mCursor = db.query(true, DATABASE_TABLE, new String[]{
82 | KEY_ROW_ID, KEY_PROF_NAME, KEY_SEED, KEY_OTP_TYPE, KEY_COUNT,
83 | KEY_DIGITS, KEY_TIME_ZONE, KEY_TIME_INTERVAL}, KEY_ROW_ID + "="
84 | + rowId, null, null, null, null, null);
85 | if (mCursor != null) {
86 | mCursor.moveToFirst();
87 | }
88 | return mCursor;
89 | }
90 |
91 | public void updateCount(int rowId, int count) {
92 | ContentValues args = new ContentValues();
93 | args.put(KEY_COUNT, count);
94 | db.update(DATABASE_TABLE, args, KEY_ROW_ID + "=" + rowId, null);
95 | }
96 |
97 | private static class DatabaseHelper extends SQLiteOpenHelper {
98 | DatabaseHelper(Context context) {
99 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
100 | Log.v("DBAdapter", "DATABASE CREATE = " + DATABASE_CREATE);
101 | }
102 |
103 | @Override
104 | public void onCreate(SQLiteDatabase db) {
105 | db.execSQL(DATABASE_CREATE);
106 | }
107 |
108 | @Override
109 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
110 | Log.i(TAG, "Upgrading database from version " + oldVersion + " to "
111 | + newVersion);
112 | if (oldVersion == 1 && newVersion == 2) {
113 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
114 | + KEY_OTP_TYPE + " integer not null DEFAULT 0");
115 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
116 | + KEY_COUNT + " integer not null DEFAULT 0");
117 | }
118 | if (oldVersion == 1 && newVersion == 3) {
119 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
120 | + KEY_OTP_TYPE + " integer not null DEFAULT 0");
121 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
122 | + KEY_COUNT + " integer not null DEFAULT 0");
123 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
124 | + KEY_DIGITS + " integer not null DEFAULT 6");
125 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
126 | + KEY_TIME_ZONE + " text not null DEFAULT GMT");
127 | }
128 | if (oldVersion == 1 && newVersion == 4) {
129 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
130 | + KEY_OTP_TYPE + " integer not null DEFAULT 0");
131 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
132 | + KEY_COUNT + " integer not null DEFAULT 0");
133 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
134 | + KEY_DIGITS + " integer not null DEFAULT 6");
135 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
136 | + KEY_TIME_ZONE + " text not null DEFAULT GMT");
137 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
138 | + KEY_TIME_INTERVAL + " integer not null DEFAULT 30");
139 | }
140 | if (oldVersion == 2 && newVersion == 3) {
141 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
142 | + KEY_DIGITS + " integer not null DEFAULT 6");
143 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
144 | + KEY_TIME_ZONE + " text not null DEFAULT GMT");
145 | }
146 | if (oldVersion == 2 && newVersion == 4) {
147 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
148 | + KEY_DIGITS + " integer not null DEFAULT 6");
149 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
150 | + KEY_TIME_ZONE + " text not null DEFAULT GMT");
151 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
152 | + KEY_TIME_INTERVAL + " integer not null DEFAULT 30");
153 | }
154 | if (oldVersion == 3 && newVersion == 4) {
155 | db.execSQL("ALTER TABLE " + DATABASE_TABLE + " ADD COLUMN "
156 | + KEY_TIME_INTERVAL + " integer not null DEFAULT 30");
157 | }
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/cry/otp/HOTP.java:
--------------------------------------------------------------------------------
1 | // "derived from OATH HOTP algorithm"
2 |
3 | package org.cry.otp;
4 |
5 | import java.security.InvalidKeyException;
6 | import java.security.NoSuchAlgorithmException;
7 |
8 | import javax.crypto.Mac;
9 | import javax.crypto.spec.SecretKeySpec;
10 |
11 | class HOTP {
12 |
13 | private static byte[] hmac_sha1(byte[] keyBytes, byte[] text)
14 | throws NoSuchAlgorithmException, InvalidKeyException {
15 | // try {
16 | Mac hmacSha1;
17 | try {
18 | hmacSha1 = Mac.getInstance("HmacSHA1");
19 | } catch (NoSuchAlgorithmException exception) {
20 | hmacSha1 = Mac.getInstance("HMAC-SHA-1");
21 | }
22 | SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
23 | hmacSha1.init(macKey);
24 | return hmacSha1.doFinal(text);
25 | }
26 |
27 | static private String generateOTP(byte[] secret, long movingFactor, int codeDigits)
28 | throws NoSuchAlgorithmException, InvalidKeyException {
29 | // put movingFactor value into text byte array
30 | StringBuilder result;
31 | byte[] text = new byte[8];
32 | for (int i = text.length - 1; i >= 0; i--) {
33 | text[i] = (byte) (movingFactor & 0xff);
34 | movingFactor >>= 8;
35 | }
36 |
37 | // compute hmac hash
38 | byte[] hash = hmac_sha1(secret, text);
39 |
40 | // put selected bytes into result int
41 | int offset = hash[hash.length - 1] & 0xf;
42 | int binary = ((hash[offset] & 0x7f) << 24)
43 | | ((hash[offset + 1] & 0xff) << 16)
44 | | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
45 |
46 | int otp = (int) (binary % Math.pow(10, codeDigits));
47 | // int otp = binary % DIGITS_POWER[codeDigits];
48 | result = new StringBuilder(Integer.toString(otp));
49 | while (result.length() < codeDigits) {
50 | result.append("0");
51 | }
52 | return result.toString();
53 | }
54 |
55 | public String gen(String seed, int count, int digits) {
56 | try {
57 | byte[] byteArray = new byte[seed.length() / 2];
58 | for (int i = 0; i < seed.length(); i += 2) {
59 | byteArray[i / 2] = (byte) Integer.parseInt(
60 | seed.substring(i, i + 2), 16);
61 | }
62 | return generateOTP(byteArray, count, digits);
63 | } catch (InvalidKeyException | NoSuchAlgorithmException e) {
64 | e.printStackTrace();
65 | }
66 | return "";
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/cry/otp/Home.java:
--------------------------------------------------------------------------------
1 | package org.cry.otp;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.AlertDialog.Builder;
5 | import android.content.ClipData;
6 | import android.content.ClipboardManager;
7 | import android.content.SharedPreferences;
8 | import android.os.Bundle;
9 | import android.preference.PreferenceManager;
10 | import android.text.Editable;
11 | import android.text.TextWatcher;
12 | import android.view.Menu;
13 | import android.view.MenuItem;
14 | import android.view.View;
15 | import android.view.View.OnClickListener;
16 | import android.widget.ArrayAdapter;
17 | import android.widget.Button;
18 | import android.widget.EditText;
19 | import android.widget.Spinner;
20 | import android.widget.TextView;
21 |
22 | import androidx.appcompat.app.AppCompatActivity;
23 | import androidx.appcompat.widget.Toolbar;
24 |
25 | import java.util.Date;
26 | import java.util.TimeZone;
27 |
28 | public class Home extends AppCompatActivity {
29 | public static final int OTP_TYPE_MOTP = 0;
30 | public static final int OTP_TYPE_HOTP = 1;
31 | public static final int OTP_TYPE_TOTP = 2;
32 | private static final int MENU_ABOUT = 1;
33 | private static final int MENU_TIME = 2;
34 | private String activeProfName = "";
35 | private String activeSeed = "";
36 | private int activeOTPType = OTP_TYPE_MOTP;
37 | private int activeCount = 0;
38 | private int activeRowId = -1;
39 | private int activeDigits = 6;
40 | private String activeZone = "GMT";
41 | private int activeTimeInterval = 30;
42 | private SharedPreferences preferences;
43 | private DBAdapter db;
44 | private ClipboardManager clipboardManager;
45 |
46 | private final OnClickListener generateMOTPListener = v -> {
47 | TextView pinEditText = findViewById(R.id.motpPinEditText);
48 | if (pinEditText != null && pinEditText.getText().length() != 4) {
49 | return;
50 | }
51 |
52 | CharSequence pin = pinEditText.getText();
53 | String key = mOTP.gen(pin == null ? "" : pin.toString(), activeSeed, activeZone);
54 | TextView keyTextView = findViewById(R.id.motpKeyTextView);
55 | if (keyTextView != null) {
56 | keyTextView.setText(key);
57 | keyTextView.setVisibility(View.VISIBLE);
58 | if (clipboardManager != null) {
59 | clipboardManager.setPrimaryClip(ClipData.newPlainText(activeProfName, key));
60 | }
61 | }
62 |
63 | pinEditText.setText("");
64 | };
65 |
66 | private final OnClickListener generateHOTPListener = new OnClickListener() {
67 |
68 | public void onClick(View v) {
69 | HOTP hotp = new HOTP();
70 | String key = hotp.gen(activeSeed, activeCount, activeDigits);
71 | TextView keyTextView = findViewById(R.id.hotpKeyTextView);
72 | if (keyTextView != null) {
73 | keyTextView.setText(key);
74 | keyTextView.setVisibility(View.VISIBLE);
75 | if (clipboardManager != null) {
76 | clipboardManager.setPrimaryClip(ClipData.newPlainText(activeProfName, key));
77 | }
78 | }
79 |
80 | SharedPreferences.Editor ed = preferences.edit();
81 | activeCount = activeCount + 1;
82 | ed.putInt("count", activeCount);
83 | ed.apply();
84 | db.open();
85 | db.updateCount(activeRowId, activeCount);
86 | db.close();
87 | }
88 | };
89 |
90 | private final OnClickListener generateTOTPListener = v -> {
91 | Spinner SHATypeSpinner = findViewById(R.id.totpSHATypeSpinner);
92 | int shaType = SHATypeSpinner.getSelectedItemPosition();
93 | String key = TOTP.gen(activeSeed, activeDigits, shaType, activeTimeInterval);
94 | TextView keyTextView = findViewById(R.id.totpKeyTextView);
95 | if (keyTextView != null) {
96 | keyTextView.setText(key);
97 | keyTextView.setVisibility(View.VISIBLE);
98 | if (clipboardManager != null) {
99 | clipboardManager.setPrimaryClip(ClipData.newPlainText(activeProfName, key));
100 | }
101 | }
102 | };
103 |
104 | @Override
105 | protected void onCreate(Bundle savedInstanceState) {
106 | super.onCreate(savedInstanceState);
107 | db = new DBAdapter(this);
108 | preferences = PreferenceManager.getDefaultSharedPreferences(this);
109 | activeProfName = preferences.getString(DBAdapter.KEY_PROF_NAME, "Profile Name");
110 | activeSeed = preferences.getString(DBAdapter.KEY_SEED, "0");
111 | activeOTPType = preferences.getInt(DBAdapter.KEY_OTP_TYPE, OTP_TYPE_MOTP);
112 | activeCount = preferences.getInt(DBAdapter.KEY_COUNT, 0);
113 | activeRowId = preferences.getInt(DBAdapter.KEY_ROW_ID, -1);
114 | activeDigits = preferences.getInt(DBAdapter.KEY_DIGITS, 6);
115 | activeZone = preferences.getString(DBAdapter.KEY_TIME_ZONE, "GMT");
116 | activeTimeInterval = preferences.getInt(DBAdapter.KEY_TIME_INTERVAL, 30);
117 | }
118 |
119 | @Override
120 | protected void onResume() {
121 | super.onResume();
122 | clipboardManager = (ClipboardManager) this.getSystemService(CLIPBOARD_SERVICE);
123 | if (activeOTPType == OTP_TYPE_HOTP) {
124 | // HOTP
125 | setContentView(R.layout.hotp_main);
126 | Button generateButton = findViewById(R.id.hotpGenerateButton);
127 | TextView profNameTextView = findViewById(R.id.hotpProfileNameTextView);
128 | profNameTextView.setText(activeProfName);
129 | generateButton.setOnClickListener(generateHOTPListener);
130 | setTitle(getString(R.string.app_name) + " - HOTP");
131 | } else if (activeOTPType == OTP_TYPE_TOTP) {
132 | setContentView(R.layout.totp_main);
133 | Button generateButton = findViewById(R.id.totpGenerateButton);
134 | TextView profNameTextView = findViewById(R.id.totpProfileNameTextView);
135 | profNameTextView.setText(activeProfName);
136 | Spinner SHATypeSpinner = findViewById(R.id.totpSHATypeSpinner);
137 | ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.SHATypes, R.layout.spinner_layout);
138 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
139 | SHATypeSpinner.setAdapter(adapter);
140 | generateButton.setOnClickListener(generateTOTPListener);
141 | setTitle(getString(R.string.app_name) + " - TOTP");
142 | } else {
143 | // mOTP
144 | setContentView(R.layout.motp_main);
145 | Button generateButton = findViewById(R.id.motpGenerateButton);
146 | TextView profNameTextView = findViewById(R.id.motpProfileNameTextView);
147 | EditText pinEditText = findViewById(R.id.motpPinEditText);
148 | profNameTextView.setText(activeProfName);
149 | generateButton.setOnClickListener(generateMOTPListener);
150 | pinEditText.addTextChangedListener(new PinTextWatcher());
151 | setTitle(getString(R.string.app_name) + " - mOTP");
152 | TextView epoch = findViewById(R.id.epochValue);
153 | long time = new Date().getTime();
154 | String epochValue = "" + time + TimeZone.getTimeZone(activeZone).getOffset(time);
155 | activeZone = preferences.getString(DBAdapter.KEY_TIME_ZONE, "GMT");
156 | epoch.setText(epochValue.substring(0, 9));
157 | }
158 |
159 | Toolbar toolbar = findViewById(R.id.toolbar);
160 | setSupportActionBar(toolbar);
161 | }
162 |
163 | @Override
164 | public boolean onCreateOptionsMenu(Menu menu) {
165 | super.onCreateOptionsMenu(menu);
166 | if (activeOTPType == OTP_TYPE_MOTP || activeOTPType == OTP_TYPE_TOTP) {
167 | menu.add(0, MENU_TIME, 0, R.string.time).setIcon(
168 | R.drawable.ic_menu_time);
169 | }
170 | menu.add(0, MENU_ABOUT, 0, R.string.about_info).setIcon(
171 | R.drawable.ic_menu_about);
172 | return true;
173 | }
174 |
175 | @Override
176 | public boolean onOptionsItemSelected(MenuItem item) {
177 | Builder builder = new AlertDialog.Builder(this);
178 | switch (item.getItemId()) {
179 | case MENU_ABOUT:
180 | builder.setTitle(getString(R.string.about_dialog_title));
181 | builder.setMessage(getString(R.string.info));
182 | builder.setPositiveButton(getString(R.string.ok), null);
183 | builder.show();
184 | return true;
185 | case MENU_TIME:
186 | builder.setTitle(getString(R.string.time));
187 | long time = new Date().getTime();
188 | String epoch = ""
189 | + (time + TimeZone.getTimeZone(activeZone).getOffset(time));
190 |
191 | if (activeOTPType == OTP_TYPE_MOTP) {
192 | epoch = epoch.substring(0, epoch.length() - 4);
193 | }
194 | builder.setMessage(getString(R.string.time) + ": " + epoch + "\n"
195 | + getString(R.string.time_zone) + ": " + activeZone);
196 | builder.setPositiveButton(getString(R.string.ok), null);
197 | builder.show();
198 | return true;
199 | }
200 | return false;
201 | }
202 |
203 | private class PinTextWatcher implements TextWatcher {
204 | public void afterTextChanged(Editable s) {
205 | Button generateButton = findViewById(R.id.motpGenerateButton);
206 | generateButton.setEnabled(s.length() == 4);
207 | }
208 |
209 | public void beforeTextChanged(CharSequence s, int start, int count,
210 | int after) {
211 | }
212 |
213 | public void onTextChanged(CharSequence s, int start, int before,
214 | int count) {
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/app/src/main/java/org/cry/otp/MD5.java:
--------------------------------------------------------------------------------
1 | package org.cry.otp;
2 |
3 | import java.security.MessageDigest;
4 | import java.security.NoSuchAlgorithmException;
5 |
6 | class MD5 {
7 | private final String hex;
8 |
9 | public MD5(final String string) {
10 | String hexValue;
11 | try {
12 | MessageDigest md = MessageDigest.getInstance("MD5");
13 | md.update(string.getBytes());
14 | byte[] hash = md.digest();
15 | StringBuilder buf = new StringBuilder(hash.length * 2);
16 | int i;
17 |
18 | for (i = 0; i < hash.length; i++) {
19 | if (((int) hash[i] & 0xff) < 0x10) {
20 | buf.append("0");
21 | }
22 |
23 | buf.append(Long.toString((int) hash[i] & 0xff, 16));
24 | }
25 |
26 | hexValue = buf.toString();
27 | } catch (NoSuchAlgorithmException exception) {
28 | hexValue = "";
29 | }
30 |
31 | hex = hexValue;
32 | }
33 |
34 | public String asHex() {
35 | return hex;
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/cry/otp/ProfileSetup.java:
--------------------------------------------------------------------------------
1 | package org.cry.otp;
2 |
3 | import android.app.AlertDialog.Builder;
4 | import android.content.SharedPreferences;
5 | import android.database.Cursor;
6 | import android.os.Bundle;
7 | import android.preference.PreferenceManager;
8 | import android.view.View;
9 | import android.view.View.OnClickListener;
10 | import android.widget.ArrayAdapter;
11 | import android.widget.Button;
12 | import android.widget.EditText;
13 | import android.widget.Spinner;
14 |
15 | import androidx.appcompat.app.AppCompatActivity;
16 | import androidx.appcompat.widget.Toolbar;
17 |
18 | public class ProfileSetup extends AppCompatActivity {
19 | private SharedPreferences preferences;
20 | private boolean editing = false;
21 | private int editingRowID = -1;
22 |
23 | private Builder builder;
24 | private DBAdapter db;
25 |
26 | private final OnClickListener motpSaveButtonListener = new OnClickListener() {
27 |
28 | public void onClick(View v) {
29 | EditText profName = findViewById(R.id.motpProfileEditText);
30 | EditText profSeed = findViewById(R.id.motpSeedEditText);
31 | Spinner spinner = findViewById(R.id.motpTimeZoneSpinner);
32 | String name = profName.getText().toString();
33 | String seed = profSeed.getText().toString();
34 | int zone = spinner.getSelectedItemPosition();
35 | String time_zone = positionToZone(zone);
36 | if (seed.length() != 20) {
37 | builder.setTitle(getString(R.string.error_title));
38 | builder.setMessage(getString(R.string.error));
39 | builder.setPositiveButton(getString(R.string.ok), null);
40 | builder.show();
41 | return;
42 | }
43 | if (checkIfInDatabase(name)) {
44 | builder.setTitle(getString(R.string.error_title));
45 | builder.setMessage(getString(R.string.error));
46 | builder.setPositiveButton(getString(R.string.ok), null);
47 | builder.show();
48 | return;
49 | }
50 |
51 | if (!editing) {
52 | insertIntoDatabaseAndFinish(name, seed, Home.OTP_TYPE_MOTP, 0, time_zone, 30);
53 | } else {
54 | updateIntoDatabaseAndFinish(name, seed, Home.OTP_TYPE_MOTP, 0, time_zone, 30);
55 | }
56 | }
57 | };
58 | private final OnClickListener hotpSaveButtonListener = new OnClickListener() {
59 | public void onClick(View v) {
60 | EditText profName = findViewById(R.id.hotpProfileEditText);
61 | EditText profSeed = findViewById(R.id.hotpSeedEditText);
62 | EditText profDigit = findViewById(R.id.hotpOutputSizeEditText);
63 | Spinner seedTypeSpinner = findViewById(R.id.hotpSeedTypeSpinner);
64 | String name = profName.getText().toString();
65 | String seed = profSeed.getText().toString();
66 | boolean hexadecimalSeed = seedTypeSpinner.getSelectedItemPosition() == 0;
67 | int digits;
68 | try {
69 | digits = Integer.parseInt(profDigit.getText().toString());
70 | } catch (NumberFormatException exception) {
71 | digits = 0;
72 | }
73 | if (hexadecimalSeed) {
74 | try {
75 | if (seed.length() % 2 != 0 || seed.length() == 0) {
76 | throw new NumberFormatException();
77 | }
78 |
79 | if (seed.substring(0, 2).toLowerCase().compareTo("0x") == 0) {
80 | seed = seed.substring(2);
81 | }
82 |
83 | for (int i = 0; i < seed.length(); i++) {
84 | Integer.parseInt(seed.substring(i, i + 1), 16);
85 | }
86 | } catch (NumberFormatException e) {
87 | e.printStackTrace();
88 | builder.setTitle(R.string.error_title);
89 | builder.setMessage(R.string.hex_seed_error);
90 | builder.setPositiveButton(getString(R.string.ok), null);
91 | builder.show();
92 | return;
93 | }
94 | } else {
95 | StringBuilder newSeed = new StringBuilder();
96 | for (int i = 0; i < seed.length(); i++) {
97 | newSeed.append(Integer.toHexString(seed.charAt(i)));
98 | }
99 |
100 | seed = newSeed.toString();
101 | }
102 |
103 | if (digits <= 0 || digits >= 10) {
104 | builder.setTitle(R.string.error_title);
105 | builder.setMessage(R.string.digit_error);
106 | builder.setPositiveButton(getString(R.string.ok), null);
107 | builder.show();
108 | return;
109 | }
110 |
111 | if (checkIfInDatabase(name)) {
112 | builder.setTitle(getString(R.string.error_title));
113 | builder.setMessage(getString(R.string.error));
114 | builder.setPositiveButton(getString(R.string.ok), null);
115 | builder.show();
116 | return;
117 | }
118 |
119 | if (!editing) {
120 | insertIntoDatabaseAndFinish(name, seed, Home.OTP_TYPE_HOTP, digits, "", 30);
121 | } else {
122 | updateIntoDatabaseAndFinish(name, seed, Home.OTP_TYPE_HOTP, digits, "", 30);
123 | }
124 | }
125 | };
126 |
127 | private final OnClickListener totpSaveButtonListener = new OnClickListener() {
128 |
129 | public void onClick(View v) {
130 | EditText profName = findViewById(R.id.totpProfileEditText);
131 | EditText profSeed = findViewById(R.id.totpSeedEditText);
132 | EditText profDigits = findViewById(R.id.totpOutputSizeEditText);
133 | Spinner seedTypeSpinner = findViewById(R.id.totpSeedTypeSpinner);
134 | Spinner timeIntervalSpinner = findViewById(R.id.totpTimeIntervalSpinner);
135 | String name = profName.getText().toString();
136 | String seed = profSeed.getText().toString();
137 | int digits;
138 | try {
139 | digits = Integer.parseInt(profDigits.getText().toString());
140 | } catch (NumberFormatException exception) {
141 | digits = 0;
142 | }
143 | boolean hexadecimalSeed = seedTypeSpinner.getSelectedItemPosition() == 0;
144 | int timeInterval = timeIntervalSpinner.getSelectedItemPosition() == 0 ? 30 : 60;
145 | if (digits <= 0 || digits >= 10) {
146 | builder.setTitle(R.string.error_title);
147 | builder.setMessage(R.string.digit_error);
148 | builder.setPositiveButton(getString(R.string.ok), null);
149 | builder.show();
150 | return;
151 | }
152 |
153 | if (hexadecimalSeed) {
154 | try {
155 | if (seed.length() % 2 != 0 || seed.length() == 0) {
156 | throw new NumberFormatException();
157 | }
158 |
159 | if (seed.substring(0, 2).toLowerCase().compareTo("0x") == 0) {
160 | seed = seed.substring(2);
161 | }
162 |
163 | for (int i = 0; i < seed.length(); i++) {
164 | Integer.parseInt(seed.substring(i, i + 1), 16);
165 | }
166 | } catch (NumberFormatException e) {
167 | e.printStackTrace();
168 | builder.setTitle(R.string.error_title);
169 | builder.setMessage(R.string.hex_seed_error);
170 | builder.setPositiveButton(getString(R.string.ok), null);
171 | builder.show();
172 | return;
173 | }
174 | } else {
175 | StringBuilder newSeed = new StringBuilder();
176 | for (int i = 0; i < seed.length(); i++) {
177 | newSeed.append(Integer.toHexString(seed.charAt(i)));
178 | }
179 |
180 | seed = newSeed.toString();
181 | }
182 |
183 | if (checkIfInDatabase(name)) {
184 | builder.setTitle(getString(R.string.error_title));
185 | builder.setMessage(getString(R.string.error));
186 | builder.setPositiveButton(getString(R.string.ok), null);
187 | builder.show();
188 | return;
189 | }
190 |
191 | if (!editing) {
192 | insertIntoDatabaseAndFinish(name, seed, Home.OTP_TYPE_TOTP, digits, "", timeInterval);
193 | } else {
194 | updateIntoDatabaseAndFinish(name, seed, Home.OTP_TYPE_TOTP, digits, "", timeInterval);
195 | }
196 | }
197 |
198 | };
199 |
200 | private static String positionToZone(int zone) {
201 | switch (zone) {
202 | case 0:
203 | return "GMT-12:00";
204 | case 1:
205 | return "GMT-11:00";
206 | case 2:
207 | return "GMT-10:00";
208 | case 3:
209 | return "GMT-09:30";
210 | case 4:
211 | return "GMT-09:00";
212 | case 5:
213 | return "GMT-08:00";
214 | case 6:
215 | return "GMT-07:00";
216 | case 7:
217 | return "GMT-06:00";
218 | case 8:
219 | return "GMT-05:00";
220 | case 9:
221 | return "GMT-04:30";
222 | case 10:
223 | return "GMT-04:00";
224 | case 11:
225 | return "GMT-03:30";
226 | case 12:
227 | return "GMT-03:00";
228 | case 13:
229 | return "GMT-02:00";
230 | case 14:
231 | return "GMT-01:00";
232 | case 16:
233 | return "GMT+01:00";
234 | case 17:
235 | return "GMT+02:00";
236 | case 18:
237 | return "GMT+03:00";
238 | case 19:
239 | return "GMT+03:30";
240 | case 20:
241 | return "GMT+04:00";
242 | case 21:
243 | return "GMT+04:30";
244 | case 22:
245 | return "GMT+05:00";
246 | case 23:
247 | return "GMT+05:30";
248 | case 24:
249 | return "GMT+05:45";
250 | case 25:
251 | return "GMT+06:00";
252 | case 26:
253 | return "GMT+06:30";
254 | case 27:
255 | return "GMT+07:00";
256 | case 28:
257 | return "GMT+08:00";
258 | case 29:
259 | return "GMT+08:45";
260 | case 30:
261 | return "GMT+09:00";
262 | case 31:
263 | return "GMT+09:30";
264 | case 32:
265 | return "GMT+10:00";
266 | case 33:
267 | return "GMT+10:30";
268 | case 34:
269 | return "GMT+11:00";
270 | case 35:
271 | return "GMT+11:30";
272 | case 36:
273 | return "GMT+12:00";
274 | case 37:
275 | return "GMT+12:45";
276 | case 38:
277 | return "GMT+13:00";
278 | case 39:
279 | return "GMT+14:00";
280 | default:
281 | return "GMT";
282 | }
283 | }
284 |
285 | private static int zoneToPosition(String zone) {
286 | if ("GMT-12:00".compareTo(zone) == 0) {
287 | return 0;
288 | } else if ("GMT-11:00".compareTo(zone) == 0) {
289 | return 1;
290 | } else if ("GMT-10:00".compareTo(zone) == 0) {
291 | return 2;
292 | } else if ("GMT-09:30".compareTo(zone) == 0) {
293 | return 3;
294 | } else if ("GMT-09:00".compareTo(zone) == 0) {
295 | return 4;
296 | } else if ("GMT-08:00".compareTo(zone) == 0) {
297 | return 5;
298 | } else if ("GMT-07:00".compareTo(zone) == 0) {
299 | return 6;
300 | } else if ("GMT-06:00".compareTo(zone) == 0) {
301 | return 7;
302 | } else if ("GMT-05:00".compareTo(zone) == 0) {
303 | return 8;
304 | } else if ("GMT-04:30".compareTo(zone) == 0) {
305 | return 9;
306 | } else if ("GMT-04:00".compareTo(zone) == 0) {
307 | return 10;
308 | } else if ("GMT-03:30".compareTo(zone) == 0) {
309 | return 11;
310 | } else if ("GMT-03:00".compareTo(zone) == 0) {
311 | return 12;
312 | } else if ("GMT-02:00".compareTo(zone) == 0) {
313 | return 13;
314 | } else if ("GMT-01:00".compareTo(zone) == 0) {
315 | return 14;
316 | } else if ("GMT".compareTo(zone) == 0) {
317 | return 15;
318 | } else if ("GMT+01:00".compareTo(zone) == 0) {
319 | return 16;
320 | } else if ("GMT+02:00".compareTo(zone) == 0) {
321 | return 17;
322 | } else if ("GMT+03:00".compareTo(zone) == 0) {
323 | return 18;
324 | } else if ("GMT+03:30".compareTo(zone) == 0) {
325 | return 19;
326 | } else if ("GMT+04:00".compareTo(zone) == 0) {
327 | return 20;
328 | } else if ("GMT+04:30".compareTo(zone) == 0) {
329 | return 21;
330 | } else if ("GMT+05:00".compareTo(zone) == 0) {
331 | return 22;
332 | } else if ("GMT+05:30".compareTo(zone) == 0) {
333 | return 23;
334 | } else if ("GMT+05:45".compareTo(zone) == 0) {
335 | return 24;
336 | } else if ("GMT+06:00".compareTo(zone) == 0) {
337 | return 25;
338 | } else if ("GMT+06:30".compareTo(zone) == 0) {
339 | return 26;
340 | } else if ("GMT+07:00".compareTo(zone) == 0) {
341 | return 27;
342 | } else if ("GMT+08:00".compareTo(zone) == 0) {
343 | return 28;
344 | } else if ("GMT+08:45".compareTo(zone) == 0) {
345 | return 29;
346 | } else if ("GMT+09:00".compareTo(zone) == 0) {
347 | return 30;
348 | } else if ("GMT+09:30".compareTo(zone) == 0) {
349 | return 31;
350 | } else if ("GMT+10:00".compareTo(zone) == 0) {
351 | return 32;
352 | } else if ("GMT+10:30".compareTo(zone) == 0) {
353 | return 33;
354 | } else if ("GMT+11:00".compareTo(zone) == 0) {
355 | return 34;
356 | } else if ("GMT+11:30".compareTo(zone) == 0) {
357 | return 35;
358 | } else if ("GMT+12:00".compareTo(zone) == 0) {
359 | return 36;
360 | } else if ("GMT+12:45".compareTo(zone) == 0) {
361 | return 37;
362 | } else if ("GMT+13:00".compareTo(zone) == 0) {
363 | return 38;
364 | } else if ("GMT+14:00".compareTo(zone) == 0) {
365 | return 39;
366 | } else {
367 | return 15;
368 | }
369 | }
370 |
371 | @Override
372 | protected void onCreate(Bundle savedInstanceState) {
373 | super.onCreate(savedInstanceState);
374 |
375 | preferences = PreferenceManager.getDefaultSharedPreferences(this);
376 | db = new DBAdapter(this);
377 | builder = new Builder(this);
378 | Bundle extras = getIntent().getExtras();
379 | editing = extras.getBoolean(Profiles.EDITING_KEY);
380 | if (editing) {
381 | editingRowID = extras.getInt(DBAdapter.KEY_ROW_ID);
382 | String name = extras.getString(DBAdapter.KEY_PROF_NAME);
383 | String seed = extras.getString(DBAdapter.KEY_SEED);
384 | int otpType = extras.getInt(DBAdapter.KEY_OTP_TYPE);
385 | int digits = extras.getInt(DBAdapter.KEY_DIGITS);
386 | String zone = extras.getString(DBAdapter.KEY_TIME_ZONE);
387 | int timeInterval = extras.getInt(DBAdapter.KEY_TIME_INTERVAL, 30);
388 | if (otpType == Home.OTP_TYPE_MOTP) {
389 | setContentView(R.layout.motp_setup);
390 | setTitle(getString(R.string.app_name) + " - mOTP");
391 | EditText profNameEditText = findViewById(R.id.motpProfileEditText);
392 | EditText seedEditText = findViewById(R.id.motpSeedEditText);
393 | Spinner spinner = findViewById(R.id.motpTimeZoneSpinner);
394 | Button saveButton = findViewById(R.id.motpSaveButton);
395 | ArrayAdapter adapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.TimeZones, R.layout.spinner_layout);
396 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
397 | spinner.setAdapter(adapter);
398 | spinner.setSelection(15);
399 | profNameEditText.setText(name);
400 | seedEditText.setText(seed);
401 | spinner.setSelection(zoneToPosition(zone));
402 | saveButton.setOnClickListener(motpSaveButtonListener);
403 | saveButton.setText(R.string.save);
404 | } else if (otpType == Home.OTP_TYPE_HOTP) {
405 | setContentView(R.layout.hotp_setup);
406 | setTitle(getString(R.string.app_name) + " - HOTP");
407 | EditText profNameEditText = findViewById(R.id.hotpProfileEditText);
408 | EditText seedEditText = findViewById(R.id.hotpSeedEditText);
409 | EditText digitEditText = findViewById(R.id.hotpOutputSizeEditText);
410 | Button saveButton = findViewById(R.id.hotpSaveButton);
411 | profNameEditText.setText(name);
412 | seedEditText.setText(seed);
413 | Spinner seedTypeSpinner = findViewById(R.id.hotpSeedTypeSpinner);
414 | ArrayAdapter adapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.SeedTypes, R.layout.spinner_layout);
415 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
416 | seedTypeSpinner.setAdapter(adapter);
417 | digitEditText.setText(String.valueOf(digits));
418 | saveButton.setOnClickListener(hotpSaveButtonListener);
419 | saveButton.setText(R.string.save);
420 | } else {
421 | // TOTP
422 | setContentView(R.layout.totp_setup);
423 | setTitle(getString(R.string.app_name) + " - TOTP");
424 | EditText profNameEditText = findViewById(R.id.totpProfileEditText);
425 | EditText seedEditText = findViewById(R.id.totpSeedEditText);
426 | EditText digitEditText = findViewById(R.id.totpOutputSizeEditText);
427 | Button saveButton = findViewById(R.id.totpSaveButton);
428 | profNameEditText.setText(name);
429 | seedEditText.setText(seed);
430 | Spinner seedTypeSpinner = findViewById(R.id.totpSeedTypeSpinner);
431 | Spinner timeIntervalSpinner = findViewById(R.id.totpTimeIntervalSpinner);
432 | ArrayAdapter seedTypesAdapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.SeedTypes, R.layout.spinner_layout);
433 | seedTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
434 | seedTypeSpinner.setAdapter(seedTypesAdapter);
435 | ArrayAdapter timeIntervalAdapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.TimeIntervals, R.layout.spinner_layout);
436 | timeIntervalAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
437 | timeIntervalSpinner.setAdapter(timeIntervalAdapter);
438 | timeIntervalSpinner.setSelection(timeInterval == 30 ? 0 : 1);
439 | digitEditText.setText(String.valueOf(digits));
440 | saveButton.setOnClickListener(totpSaveButtonListener);
441 | saveButton.setText(R.string.save);
442 | }
443 | } else {
444 | int type = extras.getInt(DBAdapter.KEY_OTP_TYPE);
445 | if (type == Home.OTP_TYPE_MOTP) {
446 | setContentView(R.layout.motp_setup);
447 | setTitle(getString(R.string.app_name) + " - mOTP");
448 | Button saveButton = findViewById(R.id.motpSaveButton);
449 | saveButton.setOnClickListener(motpSaveButtonListener);
450 | Spinner timeZoneSpinner = findViewById(R.id.motpTimeZoneSpinner);
451 | ArrayAdapter adapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.TimeZones, R.layout.spinner_layout);
452 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
453 | timeZoneSpinner.setAdapter(adapter);
454 | timeZoneSpinner.setSelection(15);
455 | } else if (type == Home.OTP_TYPE_HOTP) {
456 | setContentView(R.layout.hotp_setup);
457 | setTitle(getString(R.string.app_name) + " - HOTP");
458 | Spinner seedTypeSpinner = findViewById(R.id.hotpSeedTypeSpinner);
459 | ArrayAdapter adapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.SeedTypes, R.layout.spinner_layout);
460 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
461 | seedTypeSpinner.setAdapter(adapter);
462 | seedTypeSpinner.setSelection(0);
463 | Button saveButton = findViewById(R.id.hotpSaveButton);
464 | saveButton.setOnClickListener(hotpSaveButtonListener);
465 | } else {
466 | // TOTP
467 | setContentView(R.layout.totp_setup);
468 | setTitle(getString(R.string.app_name) + " - TOTP");
469 | Spinner seedTypeSpinner = findViewById(R.id.totpSeedTypeSpinner);
470 |
471 | ArrayAdapter seedTypeAdapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.SeedTypes, R.layout.spinner_layout);
472 | seedTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
473 | seedTypeSpinner.setAdapter(seedTypeAdapter);
474 | seedTypeSpinner.setSelection(0);
475 | Spinner timeIntervalSpinner = findViewById(R.id.totpTimeIntervalSpinner);
476 | ArrayAdapter timeIntervalAdapter = ArrayAdapter.createFromResource(getApplicationContext(), R.array.TimeIntervals, R.layout.spinner_layout);
477 | timeIntervalAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
478 | timeIntervalSpinner.setAdapter(timeIntervalAdapter);
479 | timeIntervalSpinner.setSelection(0);
480 | Button saveButton = findViewById(R.id.totpSaveButton);
481 | saveButton.setOnClickListener(totpSaveButtonListener);
482 | }
483 | }
484 |
485 | Toolbar toolbar = findViewById(R.id.toolbar);
486 | setSupportActionBar(toolbar);
487 | }
488 |
489 | private boolean checkIfInDatabase(String name) {
490 | db.open();
491 | Cursor c = db.getAllProfiles();
492 | int count = c.getCount();
493 | if (count != 0 && !editing) {
494 | c.moveToFirst();
495 | int profileNameIndex = c.getColumnIndex(DBAdapter.KEY_PROF_NAME);
496 | while (!c.isAfterLast()) {
497 | if (c.getString(profileNameIndex).equals(name)) {
498 | c.close();
499 | db.close();
500 | return true;
501 | }
502 | c.moveToNext();
503 | }
504 | }
505 |
506 | c.close();
507 | db.close();
508 | return false;
509 | }
510 |
511 | private void insertIntoDatabaseAndFinish(String name, String seed,
512 | int otpType, int digits, String time_zone,
513 | int time_interval) {
514 | db.open();
515 | int rowId = (int) db.insertProfile(name, seed, otpType, digits, time_zone, time_interval);
516 | db.close();
517 | SharedPreferences.Editor ed = preferences.edit();
518 | ed.putString(DBAdapter.KEY_PROF_NAME, name);
519 | ed.putString(DBAdapter.KEY_SEED, seed);
520 | ed.putInt(DBAdapter.KEY_OTP_TYPE, otpType);
521 | ed.putInt(DBAdapter.KEY_COUNT, 0);
522 | ed.putInt(DBAdapter.KEY_DIGITS, digits);
523 | ed.putString(DBAdapter.KEY_TIME_ZONE, time_zone);
524 | ed.putInt(DBAdapter.KEY_ROW_ID, rowId);
525 | ed.putInt(DBAdapter.KEY_TIME_INTERVAL, time_interval);
526 | ed.apply();
527 | finish();
528 | }
529 |
530 | private void updateIntoDatabaseAndFinish(String name, String seed, int otpType, int digits, String time_zone, int time_interval) {
531 | db.open();
532 | Cursor current = db.getProfile(editingRowID);
533 | int count = current.getInt(current.getColumnIndex(DBAdapter.KEY_COUNT));
534 | db.deleteProfile(editingRowID);
535 | int rowId = (int) db.insertProfile(name, seed, otpType, digits, time_zone, time_interval);
536 | db.updateCount(rowId, count);
537 | current.close();
538 | db.close();
539 | SharedPreferences.Editor ed = preferences.edit();
540 | ed.putString(DBAdapter.KEY_PROF_NAME, name);
541 | ed.putString(DBAdapter.KEY_SEED, seed);
542 | ed.putInt(DBAdapter.KEY_OTP_TYPE, otpType);
543 | ed.putInt(DBAdapter.KEY_COUNT, count);
544 | ed.putInt(DBAdapter.KEY_DIGITS, digits);
545 | ed.putString(DBAdapter.KEY_TIME_ZONE, time_zone);
546 | ed.putInt(DBAdapter.KEY_ROW_ID, rowId);
547 | ed.putInt(DBAdapter.KEY_TIME_INTERVAL, time_interval);
548 | ed.apply();
549 | finish();
550 | }
551 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/cry/otp/Profiles.java:
--------------------------------------------------------------------------------
1 | package org.cry.otp;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.AlertDialog.Builder;
5 | import android.app.Dialog;
6 | import android.content.DialogInterface;
7 | import android.content.Intent;
8 | import android.content.SharedPreferences;
9 | import android.database.Cursor;
10 | import android.preference.PreferenceManager;
11 | import android.view.Menu;
12 | import android.view.MenuItem;
13 | import android.view.View;
14 | import android.widget.AdapterView;
15 | import android.widget.AdapterView.AdapterContextMenuInfo;
16 | import android.widget.AdapterView.OnItemClickListener;
17 | import android.widget.ArrayAdapter;
18 | import android.widget.ListView;
19 |
20 | import androidx.appcompat.app.AppCompatActivity;
21 | import androidx.appcompat.widget.Toolbar;
22 |
23 | import java.util.ArrayList;
24 |
25 | public class Profiles extends AppCompatActivity {
26 | public static final String EDITING_KEY = "Editing";
27 | private static final int MENU_ADD = 1;
28 | private static final int MENU_ABOUT = 2;
29 | private static final int CONTEXT_DELETE = 0;
30 | private static final int CONTEXT_EDIT = 1;
31 | private static final int CONTEXT_SECRET = 2;
32 | private static final int DIALOG_OTP_TYPES = 0;
33 | private static final int DIALOG_ABOUT = 1;
34 | private final DialogInterface.OnClickListener otpTypeClickListener = (dialog, which) -> {
35 | final Intent intent = new Intent(Profiles.this, ProfileSetup.class);
36 | if (which == Home.OTP_TYPE_MOTP) {
37 | intent.putExtra(DBAdapter.KEY_OTP_TYPE, Home.OTP_TYPE_MOTP);
38 | startActivity(intent);
39 | dismissDialog(DIALOG_OTP_TYPES);
40 | } else if (which == Home.OTP_TYPE_HOTP) {
41 | intent.putExtra(DBAdapter.KEY_OTP_TYPE, Home.OTP_TYPE_HOTP);
42 | startActivity(intent);
43 | dismissDialog(DIALOG_OTP_TYPES);
44 | } else {
45 | // TOTP
46 | intent.putExtra(DBAdapter.KEY_OTP_TYPE, Home.OTP_TYPE_TOTP);
47 | startActivity(intent);
48 | dismissDialog(DIALOG_OTP_TYPES);
49 | }
50 | };
51 | private Cursor profilesCursor;
52 | private DBAdapter db;
53 | private ArrayAdapter listAdapter;
54 | private SharedPreferences preferences;
55 | private final OnItemClickListener profilesGridListener = new OnItemClickListener() {
56 | public void onItemClick(AdapterView> parent, View v, int position, long id) {
57 | db.open();
58 | profilesCursor = db.getAllProfiles();
59 | profilesCursor.moveToPosition(position);
60 | SharedPreferences.Editor ed = preferences.edit();
61 | int column = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_PROF_NAME);
62 | ed.putString(DBAdapter.KEY_PROF_NAME, profilesCursor.getString(column));
63 | column = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_SEED);
64 | ed.putString(DBAdapter.KEY_SEED, profilesCursor.getString(column));
65 | column = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_OTP_TYPE);
66 | ed.putInt(DBAdapter.KEY_OTP_TYPE, profilesCursor.getInt(column));
67 | column = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_COUNT);
68 | ed.putInt(DBAdapter.KEY_COUNT, profilesCursor.getInt(column));
69 | column = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_ROW_ID);
70 | ed.putInt(DBAdapter.KEY_ROW_ID, profilesCursor.getInt(column));
71 | column = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_DIGITS);
72 | ed.putInt(DBAdapter.KEY_DIGITS, profilesCursor.getInt(column));
73 | column = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_TIME_ZONE);
74 | ed.putString(DBAdapter.KEY_TIME_ZONE, profilesCursor.getString(column));
75 | column = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_TIME_INTERVAL);
76 | ed.putInt(DBAdapter.KEY_TIME_INTERVAL, profilesCursor.getInt(column));
77 | ed.apply();
78 | profilesCursor.close();
79 | db.close();
80 |
81 | Intent intent = new Intent(Profiles.this, Home.class);
82 | startActivity(intent);
83 | }
84 | };
85 | private int count;
86 |
87 | @Override
88 | protected void onResume() {
89 | super.onResume();
90 |
91 | db = new DBAdapter(this);
92 | db.open();
93 | Cursor cursor = db.getAllProfiles();
94 | if (cursor.getCount() == 0) {
95 | cursor.close();
96 | db.close();
97 | showDialog(DIALOG_OTP_TYPES);
98 | } else {
99 | cursor.close();
100 | db.close();
101 | preferences = PreferenceManager.getDefaultSharedPreferences(this);
102 | setContentView(R.layout.profiles);
103 |
104 | Toolbar toolbar = findViewById(R.id.toolbar);
105 | setSupportActionBar(toolbar);
106 | createList();
107 | }
108 | }
109 |
110 | @Override
111 | protected Dialog onCreateDialog(int id) {
112 | Dialog dialog;
113 | AlertDialog.Builder builder;
114 | if (id == DIALOG_OTP_TYPES) {
115 | final CharSequence[] items = getResources().getStringArray(R.array.OTPTypes);
116 | builder = new AlertDialog.Builder(this);
117 | builder.setTitle(R.string.otp_type_prompt);
118 | builder.setSingleChoiceItems(items, -1, otpTypeClickListener);
119 | dialog = builder.create();
120 | } else if (id == DIALOG_ABOUT) {
121 | builder = new AlertDialog.Builder(this).setTitle(R.string.about_dialog_title)
122 | .setMessage(R.string.info)
123 | .setPositiveButton(android.R.string.yes, (dialog1, which) -> {
124 | })
125 | .setIcon(android.R.drawable.ic_dialog_info);
126 | dialog = builder.create();
127 | } else {
128 | dialog = super.onCreateDialog(id);
129 | }
130 |
131 | return dialog;
132 | }
133 |
134 | @Override
135 | public boolean onCreateOptionsMenu(Menu menu) {
136 | super.onCreateOptionsMenu(menu);
137 | menu.add(0, MENU_ADD, 0, R.string.add_info);
138 | menu.add(0, MENU_ABOUT, 0, R.string.about_info);
139 | return true;
140 | }
141 |
142 | @Override
143 | public boolean onOptionsItemSelected(MenuItem item) {
144 | switch (item.getItemId()) {
145 | case MENU_ADD:
146 | showDialog(DIALOG_OTP_TYPES);
147 | return true;
148 | case MENU_ABOUT:
149 | showDialog(DIALOG_ABOUT);
150 | return true;
151 | }
152 |
153 | return false;
154 | }
155 |
156 | private void createList() {
157 | db.open();
158 | profilesCursor = db.getAllProfiles();
159 | count = profilesCursor.getCount();
160 | ArrayList profiles = new ArrayList<>();
161 | for (int i = 0; i < count; i++) {
162 | int col_index = profilesCursor.getColumnIndexOrThrow(DBAdapter.KEY_PROF_NAME);
163 | profilesCursor.moveToPosition(i);
164 | profiles.add(profilesCursor.getString(col_index));
165 | }
166 |
167 | profilesCursor.close();
168 | db.close();
169 | ListView profilesList = findViewById(R.id.profilesList);
170 | listAdapter = new ArrayAdapter<>(this, R.layout.profile_list_item, R.id.list_content, profiles);
171 | listAdapter.notifyDataSetChanged();
172 | if (profilesList != null) {
173 | profilesList.setAdapter(listAdapter);
174 | profilesList.setOnItemClickListener(profilesGridListener);
175 | profilesList.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
176 | menu.setHeaderTitle(R.string.manage_profile_title);
177 | menu.add(0, CONTEXT_DELETE, 0, R.string.delete);
178 | menu.add(0, CONTEXT_EDIT, 0, R.string.edit_profile);
179 | menu.add(0, CONTEXT_SECRET, 0, R.string.get_secret);
180 | });
181 | }
182 | }
183 |
184 | @Override
185 | public boolean onContextItemSelected(MenuItem aItem) {
186 | AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) aItem.getMenuInfo();
187 | db.open();
188 | Cursor c = db.getAllProfiles();
189 | c.moveToFirst();
190 | for (int x = 0; x < menuInfo.position; x++) {
191 | c.moveToNext();
192 | }
193 |
194 | int rowId = c.getInt(c.getColumnIndexOrThrow(DBAdapter.KEY_ROW_ID));
195 | String profName = c.getString(c.getColumnIndexOrThrow(DBAdapter.KEY_PROF_NAME));
196 | String seed = c.getString(c.getColumnIndexOrThrow(DBAdapter.KEY_SEED));
197 | int otpType = c.getInt(c.getColumnIndexOrThrow(DBAdapter.KEY_OTP_TYPE));
198 | int digits = c.getInt(c.getColumnIndexOrThrow(DBAdapter.KEY_DIGITS));
199 | String zone = c.getString(c.getColumnIndexOrThrow(DBAdapter.KEY_TIME_ZONE));
200 | int timeInterval = c.getInt(c.getColumnIndexOrThrow(DBAdapter.KEY_TIME_INTERVAL));
201 | c.close();
202 | db.close();
203 | switch (aItem.getItemId()) {
204 | case CONTEXT_DELETE:
205 | preferences = PreferenceManager.getDefaultSharedPreferences(this);
206 | db.open();
207 | db.deleteProfile(rowId);
208 | profilesCursor = db.getAllProfiles();
209 | count = profilesCursor.getCount();
210 | profilesCursor.close();
211 | db.close();
212 |
213 | if (count == 0) {
214 | db.open();
215 | db.reCreate();
216 | db.close();
217 | Intent intent = new Intent(Profiles.this, Profiles.class);
218 | startActivity(intent);
219 | finish();
220 | } else {
221 | listAdapter.remove(profName);
222 | listAdapter.notifyDataSetChanged();
223 | }
224 |
225 | return true;
226 | case CONTEXT_EDIT:
227 | final Intent intent = new Intent(Profiles.this, ProfileSetup.class);
228 | intent.putExtra(EDITING_KEY, true);
229 | intent.putExtra(DBAdapter.KEY_ROW_ID, rowId);
230 | intent.putExtra(DBAdapter.KEY_PROF_NAME, profName);
231 | intent.putExtra(DBAdapter.KEY_SEED, seed);
232 | intent.putExtra(DBAdapter.KEY_OTP_TYPE, otpType);
233 | intent.putExtra(DBAdapter.KEY_DIGITS, digits);
234 | intent.putExtra(DBAdapter.KEY_TIME_ZONE, zone);
235 | intent.putExtra(DBAdapter.KEY_TIME_INTERVAL, timeInterval);
236 | startActivity(intent);
237 | return true;
238 | case CONTEXT_SECRET:
239 | Builder builder = new AlertDialog.Builder(this);
240 | builder.setTitle(profName);
241 |
242 | if (otpType == Home.OTP_TYPE_MOTP) {
243 | String secret = new MD5(seed).asHex().substring(0, 16);
244 | builder.setMessage(getString(R.string.secret) + ": " + secret);
245 | } else {
246 | builder.setMessage(getString(R.string.secret) + ": " + seed);
247 | }
248 |
249 | builder.setPositiveButton(R.string.ok, null);
250 | builder.show();
251 | return true;
252 | }
253 |
254 | return false;
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/app/src/main/java/org/cry/otp/TOTP.java:
--------------------------------------------------------------------------------
1 | package org.cry.otp;
2 |
3 | import java.lang.reflect.UndeclaredThrowableException;
4 | import java.math.BigInteger;
5 | import java.security.GeneralSecurityException;
6 |
7 | import javax.crypto.Mac;
8 | import javax.crypto.spec.SecretKeySpec;
9 |
10 | /**
11 | * This an example implementation of the OATH TOTP algorithm. Visit
12 | * www.openauthentication.org for more information. There have been
13 | * modifications to work within the mOTP framework for OTPs by Chris Miceli
14 | *
15 | * @author Johan Rydell, PortWise, Inc.
16 | */
17 | class TOTP {
18 |
19 | private TOTP() {
20 | }
21 |
22 | /**
23 | * This method uses the JCE to provide the crypto algorithm. HMAC computes a
24 | * Hashed Message Authentication Code with the crypto hash algorithm as a
25 | * parameter.
26 | *
27 | * @param crypto the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
28 | * @param keyBytes the bytes to use for the HMAC key
29 | * @param text the message or text to be authenticated.
30 | */
31 | private static byte[] hmac_sha1(String crypto, byte[] keyBytes, byte[] text) {
32 | try {
33 | Mac hmac;
34 | hmac = Mac.getInstance(crypto);
35 | SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
36 | hmac.init(macKey);
37 | return hmac.doFinal(text);
38 | } catch (GeneralSecurityException gse) {
39 | throw new UndeclaredThrowableException(gse);
40 | }
41 | }
42 |
43 | /**
44 | * This method converts HEX string to Byte[]
45 | *
46 | * @param hex the HEX string
47 | * @return A byte array
48 | */
49 | private static byte[] hexStr2Bytes(String hex) {
50 | // Adding one byte to get the right conversion
51 | // values starting with "0" can be converted
52 | byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
53 |
54 | // Copy all the REAL bytes, not the "first"
55 | byte[] ret = new byte[bArray.length - 1];
56 | System.arraycopy(bArray, 1, ret, 0, ret.length);
57 | return ret;
58 | }
59 |
60 | public static String gen(String key, int returnDigits, int shaType, int timeInterval) {
61 | long T0 = 0;
62 |
63 | long testTime = System.currentTimeMillis() / 1000L;
64 | long T = (testTime - T0) / (long) timeInterval;
65 | StringBuilder time = new StringBuilder(Long.toHexString(T).toUpperCase());
66 | while (time.length() < 16)
67 | time.append("0");
68 |
69 | StringBuilder result;
70 | byte[] hash;
71 |
72 | // Get the HEX in a Byte[]
73 | byte[] msg = hexStr2Bytes(time.toString());
74 |
75 | // Adding one byte to get the right conversion
76 | // key = encodeHexString(key);
77 | // byte[] k = hexStr2Bytes(key);
78 |
79 | byte[] k = new byte[key.length() / 2];
80 | for (int i = 0; i < key.length(); i += 2) {
81 | k[i / 2] = (byte) Integer.parseInt(key.substring(i, i + 2), 16);
82 | }
83 |
84 | String crypto;
85 | if (shaType == 0) {
86 | crypto = "HmacSHA1";
87 | } else if (shaType == 1) {
88 | crypto = "HmacSHA256";
89 | } else {
90 | // sha 256
91 | crypto = "HmacSHA512";
92 | }
93 | hash = hmac_sha1(crypto, k, msg);
94 |
95 | // put selected bytes into result int
96 | int offset = hash[hash.length - 1] & 0xf;
97 |
98 | int binary = ((hash[offset] & 0x7f) << 24)
99 | | ((hash[offset + 1] & 0xff) << 16)
100 | | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
101 |
102 | int otp = binary % (int) (Math.pow(10, returnDigits));
103 |
104 | result = new StringBuilder(Integer.toString(otp));
105 | while (result.length() < returnDigits) {
106 | result.append("0");
107 | }
108 | return result.toString();
109 | }
110 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/cry/otp/mOTP.java:
--------------------------------------------------------------------------------
1 | package org.cry.otp;
2 |
3 | import java.util.Date;
4 | import java.util.TimeZone;
5 |
6 | class mOTP {
7 | public static String gen(String PIN, String seed, String zone) {
8 | long time = new Date().getTime();
9 | String epoch = "" + (time + TimeZone.getTimeZone(zone).getOffset(time));
10 | String secret = new MD5(seed).asHex().substring(0, 16);
11 | epoch = epoch.substring(0, epoch.length() - 4);
12 | String otp = epoch + secret + PIN;
13 | MD5 hash = new MD5(otp);
14 | otp = hash.asHex().substring(0, 6);
15 | return otp;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrismiceli/motp/aaaefe415183cf2d930fe6a3cfdfc76fce92169e/app/src/main/res/drawable/ic_menu_about.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrismiceli/motp/aaaefe415183cf2d930fe6a3cfdfc76fce92169e/app/src/main/res/drawable/ic_menu_login.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_time.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrismiceli/motp/aaaefe415183cf2d930fe6a3cfdfc76fce92169e/app/src/main/res/drawable/ic_menu_time.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrismiceli/motp/aaaefe415183cf2d930fe6a3cfdfc76fce92169e/app/src/main/res/drawable/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/hotp_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
26 |
27 |
28 |
35 |
36 |
42 |
43 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/hotp_setup.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
26 |
27 |
28 |
37 |
38 |
49 |
50 |
59 |
60 |
71 |
72 |
77 |
78 |
84 |
85 |
99 |
100 |
101 |
110 |
111 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/motp_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
26 |
27 |
28 |
35 |
36 |
42 |
43 |
49 |
50 |
62 |
63 |
64 |
70 |
71 |
78 |
79 |
84 |
85 |
90 |
91 |
97 |
98 |
99 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/motp_setup.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
26 |
27 |
28 |
37 |
38 |
49 |
50 |
59 |
60 |
66 |
67 |
78 |
79 |
80 |
89 |
90 |
96 |
97 |
102 |
103 |
111 |
112 |
113 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/profile_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/profiles.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
24 |
25 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/spinner_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/totp_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
26 |
27 |
28 |
35 |
36 |
43 |
44 |
50 |
51 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/totp_setup.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
26 |
27 |
28 |
37 |
38 |
49 |
50 |
59 |
60 |
71 |
72 |
77 |
78 |
84 |
85 |
99 |
100 |
101 |
110 |
111 |
120 |
121 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - mOTP
5 | - HOTP
6 | - TOTP
7 |
8 |
9 | - 16进制
10 | - ASCII
11 |
12 |
13 | - SHA-1
14 | - SHA-256
15 | - SHA-512
16 |
17 |
18 | - UTC -12
19 | - UTC -11
20 | - UTC -10
21 | - UTC -09:30
22 | - UTC -09
23 | - UTC -08
24 | - UTC -07
25 | - UTC -06
26 | - UTC -05
27 | - UTC -04:30
28 | - UTC -04
29 | - UTC -03:30
30 | - UTC -03
31 | - UTC -02
32 | - UTC -01
33 | - UTC
34 | - UTC +01
35 | - UTC +02
36 | - UTC +03
37 | - UTC +03:30
38 | - UTC +04
39 | - UTC +04:30
40 | - UTC +05
41 | - UTC +05:30
42 | - UTC +05:45
43 | - UTC +06
44 | - UTC +06:30
45 | - UTC +07
46 | - UTC +08
47 | - UTC +08:45
48 | - UTC +09
49 | - UTC +09:30
50 | - UTC +10
51 | - UTC +10:30
52 | - UTC +11
53 | - UTC +11:30
54 | - UTC +12
55 | - UTC +12:45
56 | - UTC +13
57 | - UTC +14
58 |
59 |
60 | - 30 秒
61 | - 60 秒
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 关于
4 | 添加配置
5 | mOTP
6 | 关于
7 | 更改配置
8 | 删除
9 | 错误!
10 | 生成密钥
11 | 查看秘密
12 | 密钥
13 | 管理配置
14 | 确定
15 | 密码:
16 | 配置名:
17 | 配置名
18 | 秘密
19 | 数种子:
20 | 请注意,这是生成数种子的密钥。必须包含20个字符,只能使用字母数字字符 - 无符号\n\n为了找到密钥,配置后,长按配置,然后选择“查看秘密”
21 | 时间
22 | 时区
23 | 时代:
24 | 尚未确定
25 | 参考:\nmOTP - http://motp.sf.net\nHOTP - RFC 4226
26 | \nTOTP - http://tools.ietf.org/html/draft-mraihi-totp-timebased-05\n\n
27 | 程序员:\nChristopher Miceli\nMichael Miceli
28 | 名称必须至少4个字符。
29 | \n\n蜜种子必须包含20个字符。\n\n不能有重复的配置名。
30 | 数种子只能用0–9 A-F,还有必须位必须为偶数.
31 | 数种子种类
32 | 数字长度必须介于1到10数字。
33 | 选择一个OTP
34 | 选择一个SHA家族
35 | 數字:
36 | 保存
37 | 时间间隔
38 | 6
39 | 数种子
40 | 密码
41 | 數字
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - mOTP
5 | - HOTP
6 | - TOTP
7 |
8 |
9 | - 16進制
10 | - ASCII
11 |
12 |
13 | - SHA-1
14 | - SHA-256
15 | - SHA-512
16 |
17 |
18 | - UTC -12
19 | - UTC -11
20 | - UTC -10
21 | - UTC -09:30
22 | - UTC -09
23 | - UTC -08
24 | - UTC -07
25 | - UTC -06
26 | - UTC -05
27 | - UTC -04:30
28 | - UTC -04
29 | - UTC -03:30
30 | - UTC -03
31 | - UTC -02
32 | - UTC -01
33 | - UTC
34 | - UTC +01
35 | - UTC +02
36 | - UTC +03
37 | - UTC +03:30
38 | - UTC +04
39 | - UTC +04:30
40 | - UTC +05
41 | - UTC +05:30
42 | - UTC +05:45
43 | - UTC +06
44 | - UTC +06:30
45 | - UTC +07
46 | - UTC +08
47 | - UTC +08:45
48 | - UTC +09
49 | - UTC +09:30
50 | - UTC +10
51 | - UTC +10:30
52 | - UTC +11
53 | - UTC +11:30
54 | - UTC +12
55 | - UTC +12:45
56 | - UTC +13
57 | - UTC +14
58 |
59 |
60 | - 30 秒
61 | - 60 秒
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 關於
4 | 添加配置
5 | mOTP
6 | 關於
7 | 更改配置
8 | 刪除
9 | 錯誤!
10 | 生成密鑰
11 | 查看秘密
12 | 密鑰
13 | 管理配置
14 | 確定
15 | 密碼:
16 | 配置名:
17 | 配置名
18 | 秘密
19 | 數種子:
20 | 請注意,這是生成數種子的密鑰。必須包含20個字符,隻能使用字母數字字符 - 無符號\n\n為了找到密鑰,配置后,長按配置,然后選擇“查看秘密”
21 | 時間
22 | 時區
23 | 時代:
24 | 尚未確定
25 | 參考:\nmOTP - http://motp.sf.net\nHOTP - RFC 4226
26 | \nTOTP - http://tools.ietf.org/html/draft-mraihi-totp-timebased-05
27 | \n\n程序員:\nChristopher Miceli\nMichael Miceli
28 | 名稱必須至少4個字符。
29 | \n\n蜜種子必須包含20個字符。\n\n不能有重復的配置名。
30 | 數種子隻能用0–9 A-F,還有必須位必須為偶數.
31 | 數種子種類
32 | 數字長度必須介於1到10數字。
33 | 選擇一個OTP
34 | 選擇一個SHA家族
35 | 數字:
36 | 保存
37 | 時間間隔
38 | 6
39 | 數種子
40 | 密碼
41 | 數字
42 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - mOTP
5 | - HOTP
6 | - TOTP
7 |
8 |
9 | - Hexadecimal
10 | - ASCII
11 |
12 |
13 | - SHA-1
14 | - SHA-256
15 | - SHA-512
16 |
17 |
18 | - UTC -12
19 | - UTC -11
20 | - UTC -10
21 | - UTC -09:30
22 | - UTC -09
23 | - UTC -08
24 | - UTC -07
25 | - UTC -06
26 | - UTC -05
27 | - UTC -04:30
28 | - UTC -04
29 | - UTC -03:30
30 | - UTC -03
31 | - UTC -02
32 | - UTC -01
33 | - UTC
34 | - UTC +01
35 | - UTC +02
36 | - UTC +03
37 | - UTC +03:30
38 | - UTC +04
39 | - UTC +04:30
40 | - UTC +05
41 | - UTC +05:30
42 | - UTC +05:45
43 | - UTC +06
44 | - UTC +06:30
45 | - UTC +07
46 | - UTC +08
47 | - UTC +08:45
48 | - UTC +09
49 | - UTC +09:30
50 | - UTC +10
51 | - UTC +10:30
52 | - UTC +11
53 | - UTC +11:30
54 | - UTC +12
55 | - UTC +12:45
56 | - UTC +13
57 | - UTC +14
58 |
59 |
60 | - 30 Seconds
61 | - 60 Seconds
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | About
4 | Add Profile
5 | mOTP
6 | About
7 | Edit Profile
8 | Delete
9 | Error!
10 | Generate Key
11 | View Secret
12 | Key
13 | Manage Profile
14 | OK
15 | PIN:
16 | Profile Name:
17 | Profile Name
18 | Secret
19 | Seed:
20 | Please note, this is the seed which generates your secret key. It should be exactly 20 characters long, and be only alpha-numeric characters only - no symbols please.\n\nTo find your secret key, once you have completed this screen, please long-press on the profile, and select "View Secret"
21 | Time
22 | Time Zone
23 | Epoch:
24 | Uncalculated
25 | Reference:\nmOTP - http://motp.sf.net\nHOTP - RFC 4226
26 | \nTOTP - http://tools.ietf.org/html/draft-mraihi-totp-timebased-05
27 | \n\nProgrammers:\nChristopher Miceli\nMichael Miceli
28 | Name needs to be larger than 4 character.
29 | \n\nSeed length must be 20 characters.\n\nCan not have duplicate profile names.
30 | Seed must be 0–9 A-F and of non-zero even length.
31 | Seed Type
32 | The output size must be between 1 and 10 digits long.
33 | Select an OTP type
34 | Select a SHA type
35 | Digits:
36 | Save
37 | Time Interval
38 | 6
39 | Seed
40 | PIN
41 | Digits
42 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/.name:
--------------------------------------------------------------------------------
1 | otp
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/otp.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/resources/org/cry/otp/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | 1463448016154
182 |
183 | 1463448016154
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:8.5.1'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | android.defaults.buildfeatures.buildconfig=true
21 | android.nonTransitiveRClass=false
22 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrismiceli/motp/aaaefe415183cf2d930fe6a3cfdfc76fce92169e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Dec 31 20:47:46 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
--------------------------------------------------------------------------------