check daemon availability

show txs immediately
only save on first sync

no store on close

Increased Version
This commit is contained in:
m2049r 2017-08-08 22:06:48 +02:00
parent cb5795e64b
commit 95e47e3407
17 changed files with 175 additions and 96 deletions

View File

@ -89,6 +89,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/cmake" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />

View File

@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
versionCode 2
versionName "0.2.0"
versionCode 3
versionName "0.3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {

View File

@ -386,8 +386,9 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setDaemonAddressJ(JNIEnv *env, job
env->ReleaseStringUTFChars(address, _address);
}
// returns whether the daemon can be reached, and its version number
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getConnectedDaemonVersion(JNIEnv *env,
Java_com_m2049r_xmrwallet_model_WalletManager_getDaemonVersion(JNIEnv *env,
jobject instance) {
uint32_t version;
bool isConnected =

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -25,6 +25,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.KeyEvent;
@ -46,11 +47,19 @@ import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class LoginActivity extends Activity {
static final String TAG = "LoginActivity";
static final int MIN_DAEMON_VERSION = 65544;
static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms
ListView listView;
List<String> walletList = new ArrayList<>();
List<String> displayedList = new ArrayList<>();
@ -124,9 +133,17 @@ public class LoginActivity extends Activity {
final int preambleLength = "[123456] ".length();
if (itemValue.length() <= (preambleLength)) {
Toast.makeText(LoginActivity.this, "something's wrong", Toast.LENGTH_LONG).show();
Toast.makeText(LoginActivity.this, getString(R.string.panic), Toast.LENGTH_LONG).show();
return;
}
setWalletDaemon();
if (!checkWalletDaemon()) {
Toast.makeText(LoginActivity.this, getString(R.string.warn_daemon_unavailable), Toast.LENGTH_LONG).show();
return;
}
// looking good
savePrefs(false);
String wallet = itemValue.substring(preambleLength);
promptPassword(wallet);
@ -200,6 +217,32 @@ public class LoginActivity extends Activity {
}
}
private boolean checkWalletDaemon() {
// if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy prevPolicy = StrictMode.getThreadPolicy();
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder(prevPolicy).permitNetwork().build();
StrictMode.setThreadPolicy(policy);
String d[] = WalletManager.getInstance().getDaemonAddress().split(":");
String host = d[0];
int port = Integer.parseInt(d[1]);
Socket socket = new Socket();
long a = new Date().getTime();
try {
socket.connect(new InetSocketAddress(host, port), DAEMON_TIMEOUT);
socket.close();
} catch (IOException ex) {
Log.d(TAG, "Cannot reach daemon " + host + ":" + port + " because " + ex.getLocalizedMessage());
return false;
} finally {
StrictMode.setThreadPolicy(prevPolicy);
}
long b = new Date().getTime();
Log.d(TAG, "Daemon is " + (b - a) + "ms away.");
int version = WalletManager.getInstance().getDaemonVersion();
Log.d(TAG, "Daemon is v" + version);
return (version >= MIN_DAEMON_VERSION);
}
private boolean checkWalletPassword(String walletName, String password) {
String walletPath = new File(Helper.getStorageRoot(getApplicationContext()),
walletName + ".keys").getAbsolutePath();
@ -207,7 +250,6 @@ public class LoginActivity extends Activity {
return WalletManager.getInstance().verifyWalletPassword(walletPath, password, true);
}
@Override
protected void onPause() {
Log.d(TAG, "onPause()");
@ -215,6 +257,12 @@ public class LoginActivity extends Activity {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
}
boolean isMainNet() {
ToggleButton tbMainNet = (ToggleButton) findViewById(R.id.tbMainNet);
return tbMainNet.isChecked();
@ -275,19 +323,20 @@ public class LoginActivity extends Activity {
editor.apply();
}
void startWallet(String walletName, String walletPassword) {
Log.d(TAG, "startWallet()");
savePrefs(false);
private void setWalletDaemon() {
boolean testnet = !isMainNet();
String daemon = getDaemon();
Intent intent = new Intent(getApplicationContext(), WalletActivity.class);
if (!daemon.contains(":")) {
daemon = daemon + (testnet ? ":28081" : ":18081");
}
WalletManager.getInstance().setDaemon(daemon, testnet);
}
void startWallet(String walletName, String walletPassword) {
Log.d(TAG, "startWallet()");
Intent intent = new Intent(getApplicationContext(), WalletActivity.class);
intent.putExtra(WalletActivity.REQUEST_ID, walletName);
intent.putExtra(WalletActivity.REQUEST_PW, walletPassword);
startActivity(intent);
@ -332,7 +381,7 @@ public class LoginActivity extends Activity {
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],@NonNull int[] grantResults) {
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Log.d(TAG, "onRequestPermissionsResult()");
switch (requestCode) {
case Helper.PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE:

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -97,6 +97,7 @@ public class WalletActivity extends AppCompatActivity
Log.d(TAG, "onStop()");
releaseWakeLock();
disconnectWalletService();
this.synced = false;
super.onStop();
}
@ -145,6 +146,7 @@ public class WalletActivity extends AppCompatActivity
balanceView.setText(Wallet.getDisplayAmount(wallet.getBalance()));
unlockedView.setText(Wallet.getDisplayAmount(wallet.getUnlockedBalance()));
String sync = "";
// TODO: getConnectionStatus() blocks as it tries to connect - this is bad in the UI thread!
if (wallet.getConnectionStatus() == Wallet.ConnectionStatus.ConnectionStatus_Connected) {
if (!wallet.isSynchronized()) {
long n = wallet.getDaemonBlockChainHeight() - wallet.getBlockChainHeight();
@ -160,13 +162,19 @@ public class WalletActivity extends AppCompatActivity
sync = getString(R.string.status_synced) + ": " + wallet.getBlockChainHeight();
if (!synced) {
hideProgress();
saveWallet(); // save ONLY on first sync
// the usual use case is:
// open the wallet, wait for sync, check balance, close app
// even if we wait for new transactions, they will be synced and saved next time
// the advantage here is that we are storing the state while the app is open
// and don't get into timing issues
synced = true;
}
}
}
String t = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
syncProgressView.setText(sync);
connectionStatusView.setText(t + " " + wallet.getConnectionStatus().toString().substring(17));
connectionStatusView.setText(net + " " + wallet.getConnectionStatus().toString().substring(17));
}
@Override
@ -241,11 +249,22 @@ public class WalletActivity extends AppCompatActivity
mBoundService.setObserver(null);
unbindService(mConnection);
mIsBound = false;
Toast.makeText(getApplicationContext(), getString(R.string.status_wallet_unloading), Toast.LENGTH_LONG).show();
Log.d(TAG, "UNBOUND");
}
}
void saveWallet() {
if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class);
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_STORE);
startService(intent);
Toast.makeText(getApplicationContext(), getString(R.string.status_wallet_unloading), Toast.LENGTH_LONG).show();
Log.d(TAG, "STORE request sent");
} else {
Log.e(TAG, "Service not bound");
}
}
// Callbacks from TransactionInfoAdapter
@Override
public void onInteraction(final View view, final TransactionInfo infoItem) {
@ -279,6 +298,7 @@ public class WalletActivity extends AppCompatActivity
@Override
protected void onPause() {
Log.d(TAG, "onPause()");
//saveWallet(); //TODO: do it here if we really need to ...
super.onPause();
}

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -38,10 +38,10 @@ import java.util.List;
import java.util.TimeZone;
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
static final String TAG = "TransactionInfoAdapter";
private static final String TAG = "TransactionInfoAdapter";
static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
private static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
static final int TX_RED = Color.rgb(255, 79, 65);
static final int TX_GREEN = Color.rgb(54, 176, 91);
@ -100,15 +100,15 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public final TextView tvAmount;
public final TextView tvAmountPoint;
public final TextView tvAmountDecimal;
public final TextView tvDate;
public final TextView tvTime;
public TransactionInfo infoItem;
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView tvAmount;
final TextView tvAmountPoint;
final TextView tvAmountDecimal;
final TextView tvDate;
final TextView tvTime;
TransactionInfo infoItem;
public ViewHolder(View itemView) {
ViewHolder(View itemView) {
super(itemView);
this.tvAmount = (TextView) itemView.findViewById(R.id.tx_amount);
// I know this is stupid but can't be bothered to align decimals otherwise

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -23,7 +23,7 @@ public class TransactionInfo {
public long handle;
public TransactionInfo(long handle) {
TransactionInfo(long handle) {
this.handle = handle;
}

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -35,7 +35,7 @@ public class Wallet {
private long handle = 0;
private long listenerHandle = 0;
public Wallet(long handle) {
Wallet(long handle) {
this.handle = handle;
}

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -29,7 +29,7 @@ import java.util.List;
import java.util.Map;
public class WalletManager {
final static String TAG = "WalletManager";
private final static String TAG = "WalletManager";
static {
System.loadLibrary("monerujo");
@ -204,7 +204,7 @@ public class WalletManager {
private native void setDaemonAddressJ(String address);
public native int getConnectedDaemonVersion();
public native int getDaemonVersion();
public native long getBlockchainHeight();

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,9 +16,7 @@
package com.m2049r.xmrwallet.service;
import android.app.ProgressDialog;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@ -27,10 +25,8 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.util.Log;
import android.widget.Toast;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
@ -47,10 +43,11 @@ import com.m2049r.xmrwallet.util.Helper;
public class WalletService extends Service {
final static String TAG = "WalletService";
public static final String REQUEST = "request";
public static final String REQUEST_WALLET = "wallet";
public static final String REQUEST = "request";
public static final String REQUEST_CMD_LOAD = "load";
public static final String REQUEST_CMD_LOAD_PW = "walletPassword";
public static final String REQUEST_CMD_STORE = "store";
public static final int START_SERVICE = 1;
public static final int STOP_SERVICE = 2;
@ -70,7 +67,7 @@ public class WalletService extends Service {
this.wallet = aWallet;
}
public void start() {
void start() {
Log.d(TAG, "MyWalletListener.start()");
if (wallet == null) throw new IllegalStateException("No wallet!");
//acquireWakeLock();
@ -78,7 +75,7 @@ public class WalletService extends Service {
wallet.startRefresh();
}
public void stop() {
void stop() {
Log.d(TAG, "MyWalletListener.stop()");
if (wallet == null) throw new IllegalStateException("No wallet!");
wallet.pauseRefresh();
@ -178,7 +175,7 @@ public class WalletService extends Service {
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
ServiceHandler(Looper looper) {
super(looper);
}
@ -188,11 +185,19 @@ public class WalletService extends Service {
switch (msg.arg2) {
case START_SERVICE: {
Bundle extras = msg.getData();
String walletId = extras.getString(REQUEST_WALLET, null);
String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null);
Log.d(TAG, "LOAD wallet " + walletId);// + ":" + walletPw);
if (walletId != null) {
start(walletId, walletPw); // TODO What if this fails?
String cmd = extras.getString(REQUEST, null);
if (cmd.equals(REQUEST_CMD_LOAD)) {
String walletId = extras.getString(REQUEST_WALLET, null);
String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null);
Log.d(TAG, "LOAD wallet " + walletId);// + ":" + walletPw);
if (walletId != null) {
start(walletId, walletPw); // TODO What if this fails?
}
} else if (cmd.equals(REQUEST_CMD_STORE)) {
Wallet myWallet = getWallet();
Log.d(TAG, "storing wallet: " + myWallet.getName());
getWallet().store();
Log.d(TAG, "wallet stored: " + myWallet.getName());
}
}
break;
@ -251,7 +256,6 @@ public class WalletService extends Service {
// this should not matter since the old activity is not getting updates
// and the new one is not listening yet (although it will be bound)
Log.d(TAG, "onStartCommand()");
//acquireWakeLock(); // we want to be awake for the fun stuff
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
@ -293,6 +297,11 @@ public class WalletService extends Service {
showProgress(95);
}
Log.d(TAG, "start() done");
if (observer != null) {
Wallet myWallet = getWallet();
myWallet.getHistory().refresh();
observer.onRefreshed(myWallet, true);
}
}
public void stop() {

View File

@ -1,12 +1,12 @@
/**
/*
* Copyright (c) 2017 m2049r
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -28,8 +28,8 @@ import com.m2049r.xmrwallet.R;
import java.io.File;
public class Helper {
static final String TAG = "Helper";
static final String WALLET_DIR = "Monerujo";
private static final String TAG = "Helper";
private static final String WALLET_DIR = "Monerujo";
static public File getStorageRoot(Context context) {
if (!isExternalStorageWritable()) {
@ -79,10 +79,7 @@ public class Helper {
/* Checks if external storage is available for read and write */
static public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
return Environment.MEDIA_MOUNTED.equals(state);
}
}

View File

@ -62,7 +62,7 @@
android:textSize="10sp"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
android:text="Connecting..."
android:text="Loading..."
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -73,7 +73,7 @@
android:textSize="10sp"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
android:text="Connecting..."
android:text="Loading..."
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvConnectionStatus" />
@ -81,8 +81,8 @@
<LinearLayout
android:id="@+id/llProgress"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone" >
@ -94,7 +94,7 @@
android:textSize="16sp"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
android:text="Connecting..." />
android:text="Loading..." />
<ProgressBar
android:id="@+id/pbProgress"

View File

@ -14,7 +14,9 @@
<string name="prompt_password">Password for</string>
<string name="bad_password">Bad password!</string>
<string name="prompt_daemon_missing">Daemon address must be set!</string>
<string name="prompt_wrong_net">Daemon does not fit wallet!</string>
<string name="prompt_wrong_net">Daemon type does not fit to wallet!</string>
<string name="warn_daemon_unavailable">Warning: cannot reach daemon!</string>
<string name="panic">Something\'s wrong!</string>
<string name="title_amount">Amount</string>
<string name="title_date">Date</string>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="xmrwallet" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<module external.linked.project.id="xmrwallet" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
@ -13,7 +13,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>