├── .gitignore
├── README.md
├── app
├── .gitignore
├── app.iml
├── build.gradle
├── proguard-rules.pro
├── release
│ └── app-release.apk
└── src
│ ├── androidTest
│ └── java
│ │ └── org
│ │ └── aeon
│ │ └── aeondaemon
│ │ └── app
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── org
│ │ │ └── aeon
│ │ │ └── aeondaemon
│ │ │ └── app
│ │ │ ├── AboutActivity.java
│ │ │ ├── AppCompatPreferenceActivity.java
│ │ │ ├── Fragments
│ │ │ ├── LogSlideFragment.java
│ │ │ └── MainSlideFragment.java
│ │ │ ├── MainActivity.java
│ │ │ ├── SettingsPrefActivity.java
│ │ │ ├── ThemeActivity.java
│ │ │ └── model
│ │ │ ├── CollectPreferences.java
│ │ │ ├── Launcher.java
│ │ │ ├── Settings.java
│ │ │ └── SynchronizeThread.java
│ └── res
│ │ ├── drawable
│ │ ├── actionbar.png
│ │ ├── background.png
│ │ ├── coins.png
│ │ ├── coinscolor.png
│ │ ├── mic_launcher.png
│ │ ├── mythbar.png
│ │ ├── mythology.png
│ │ ├── oval_selected.xml
│ │ ├── oval_unselected.xml
│ │ ├── solidcolor.png
│ │ ├── w001.png
│ │ ├── wcoin.png
│ │ ├── wowario.jpg
│ │ └── wsolidcolor.png
│ │ ├── layout
│ │ ├── activity_about.xml
│ │ ├── activity_main.xml
│ │ ├── activity_settings.xml
│ │ ├── activity_theme.xml
│ │ ├── log_fragment.xml
│ │ └── main_fragment.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── wic_launcher.png
│ │ └── wic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── wic_launcher.png
│ │ └── wic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── wic_launcher.png
│ │ └── wic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── wic_launcher.png
│ │ └── wic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── wic_launcher.png
│ │ └── wic_launcher_round.png
│ │ ├── raw
│ │ ├── monerod32
│ │ └── monerod64
│ │ ├── values-fr
│ │ └── strings.xml
│ │ ├── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ ├── arrays.xml
│ │ ├── pref_about.xml
│ │ ├── pref_settings.xml
│ │ └── pref_theme.xml
│ └── test
│ └── java
│ └── org
│ └── aeon
│ └── aeondaemon
│ └── app
│ └── ExampleUnitTest.java
├── assets
└── pocketnode-inspiration.png
├── build.gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── monero-logo.svg
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | local.properties
2 | build
3 | gradle
4 | .gradle
5 | .idea
6 | .vscode
7 | app/release/*
8 | *.jks
9 | keystore.jks
10 | scripts
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Archiving this project 10/16/23: This project is no longer being maintained. Forks welcome.
2 |
3 |
4 | # Monero PocketNode - A Monero node for your Android Device.
5 |
6 | ## Overview
7 |
8 | THIS IS A WORK IN PROGRESS. USE AT YOUR OWN RISK. PR'S/FORKS WELCOME! RECOMMEND USING ON AN OLD PHONE- NOT YOUR DAILY DRIVER. I DON'T KNOW HOW TO WRITE ANDROID APPS.
9 |
10 | This app is meant first and foremost for people with Android phones that have SD slots that are laying in a drawer that can be repurposed into a constantly-plugged-in, extremely low power node. I don't recommend using this on internal storage (though I am using this myself)... You're taking the internal SSD life into your own hands if you use this feature.
11 |
12 | Important things that probably don't work:
13 |
14 | 1. Syncing with the screen off with the phone UNPLUGGED (it SHOULD continue syncing when plugged in). You can download an app called 'Coffee' if you need to have the phone continue syncing while not plugged in.
15 |
16 | ## Device requirements
17 | A 64 bit processor with 2 GB of RAM and for storage, an SD slot or 128 GB of internal storage is recommended to run on the mainnet blockchain.
18 |
19 | ## Install
20 |
21 | Important: After installing, be sure to turn of Android battery optimization for this app, otherwise Android will auto stop it after awhile.
22 | It's typically found somewhere like: Settings > Apps > Pocket Node > App Battery Usage > Turn on Unrestricted (not Optimized).
23 |
24 | ### Secure Install
25 | Download Android Studio, replace the monero64/32 binaries with your own Android ARMv7/v8 binaries and generate a signed .apk.
26 |
27 | OR
28 |
29 | ### Just Trust CryptoGrampy Simple Install
30 | (If you trust that I haven't added nefarious binaries) Download the .apk from the (latest release)[https://github.com/CryptoGrampy/xmr-pocket-node/releases], and start up the app.
31 |
32 | ## Run it
33 |
34 | Again, first make sure battery optimization is turned off or set to unrestricted for PocketNode.
35 |
36 | To Start the node- hit the toggle on the Main screen. Check out the settings menu for more fine-grained control over your Node. That's it!
37 |
38 | You can also expose your new node as a Tor hidden service by downloading Orbot from FDroid, and going through their create hidden service feature in the top right corner. Point the hidden service service at port 18081 (this is your node's RPC port), and you can expose whatever port you want- 80, 18081, etc.
39 |
40 | ## Donate
41 |
42 | If you want to buy coffee for an 85 year old: 8BudmXKZwpXhfVGCtgFPyKWgLcDLYJ5jRT95xCp4JMFWapgTLrun41AG6LPbef7WFA8T531QGnZT51cDF6uF9HECDhibEVw
43 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.aeon.aeondaemon.app; 17 | 18 | import android.app.AlertDialog; 19 | import android.content.ClipData; 20 | import android.content.ClipboardManager; 21 | import android.content.Context; 22 | import android.content.DialogInterface; 23 | import android.graphics.drawable.ColorDrawable; 24 | import android.os.Build; 25 | import android.os.Bundle; 26 | import android.support.v4.content.ContextCompat; 27 | import android.support.v7.app.ActionBar; 28 | import android.text.method.LinkMovementMethod; 29 | import android.view.MenuItem; 30 | import android.view.View; 31 | import android.widget.TextView; 32 | 33 | public class AboutActivity extends AppCompatPreferenceActivity { 34 | private static final String TAG = AboutActivity.class.getSimpleName(); 35 | private Context context = null; 36 | 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | context = getApplicationContext(); 42 | 43 | setContentView(R.layout.activity_about); 44 | MainActivity.setAboutActivity(this); 45 | 46 | TextView t = ((TextView)findViewById(R.id.xmr_msg_val)); 47 | t.setOnClickListener(copyListener); 48 | 49 | t = ((TextView)findViewById(R.id.xmr_msg_val)); 50 | t.setOnClickListener(copyListener); 51 | 52 | t = ((TextView)findViewById(R.id.about_msg)); 53 | t.setMovementMethod(LinkMovementMethod.getInstance()); 54 | 55 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 56 | getSupportActionBar().setHomeButtonEnabled(true); 57 | getSupportActionBar().setDisplayShowHomeEnabled(true); 58 | 59 | setTheme(MainActivity.getStyle(context)); 60 | } 61 | 62 | 63 | private View.OnClickListener copyListener = new View.OnClickListener() { 64 | public void onClick(View v) { 65 | ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 66 | ClipData clip = ClipData.newPlainText("label",((TextView)v).getText()); 67 | clipboard.setPrimaryClip(clip); 68 | 69 | AlertDialog.Builder builder; 70 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 71 | builder = new AlertDialog.Builder(AboutActivity.this, android.R.style.Theme_Material_Dialog_Alert); 72 | } else { 73 | builder = new AlertDialog.Builder(AboutActivity.this); 74 | } 75 | builder.setTitle("Clipboard") 76 | .setMessage(R.string.address_selected_msg) 77 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 78 | public void onClick(DialogInterface dialog, int which) { 79 | } 80 | }) 81 | .setIcon(android.R.drawable.ic_dialog_alert) 82 | .show(); 83 | } 84 | }; 85 | 86 | @Override 87 | public boolean onOptionsItemSelected(MenuItem item) { 88 | if (item.getItemId() == android.R.id.home) { 89 | onBackPressed(); 90 | } 91 | return super.onOptionsItemSelected(item); 92 | } 93 | 94 | @Override 95 | public void onResume(){ 96 | super.onResume(); 97 | 98 | setTheme(MainActivity.getStyle(context)); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/org/aeon/aeondaemon/app/AppCompatPreferenceActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 enerc 3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.aeon.aeondaemon.app; 17 | 18 | import android.content.res.Configuration; 19 | import android.os.Bundle; 20 | import android.preference.PreferenceActivity; 21 | import android.support.annotation.LayoutRes; 22 | import android.support.annotation.Nullable; 23 | import android.support.v4.content.ContextCompat; 24 | import android.support.v7.app.ActionBar; 25 | import android.support.v7.app.AppCompatDelegate; 26 | import android.support.v7.widget.Toolbar; 27 | import android.view.MenuInflater; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | 31 | public abstract class AppCompatPreferenceActivity extends PreferenceActivity { 32 | 33 | private AppCompatDelegate mDelegate; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | getDelegate().installViewFactory(); 38 | getDelegate().onCreate(savedInstanceState); 39 | 40 | setTheme(MainActivity.getStyle(getApplicationContext())); 41 | 42 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 43 | getSupportActionBar().setHomeButtonEnabled(true); 44 | getSupportActionBar().setDisplayShowHomeEnabled(true); 45 | 46 | super.onCreate(savedInstanceState); 47 | } 48 | 49 | @Override 50 | protected void onPostCreate(Bundle savedInstanceState) { 51 | super.onPostCreate(savedInstanceState); 52 | getDelegate().onPostCreate(savedInstanceState); 53 | } 54 | 55 | public ActionBar getSupportActionBar() { 56 | return getDelegate().getSupportActionBar(); 57 | } 58 | 59 | public void setSupportActionBar(@Nullable Toolbar toolbar) { 60 | getDelegate().setSupportActionBar(toolbar); 61 | } 62 | 63 | @Override 64 | public MenuInflater getMenuInflater() { 65 | return getDelegate().getMenuInflater(); 66 | } 67 | 68 | @Override 69 | public void setContentView(@LayoutRes int layoutResID) { 70 | getDelegate().setContentView(layoutResID); 71 | } 72 | 73 | @Override 74 | public void setContentView(View view) { 75 | getDelegate().setContentView(view); 76 | } 77 | 78 | @Override 79 | public void setContentView(View view, ViewGroup.LayoutParams params) { 80 | getDelegate().setContentView(view, params); 81 | } 82 | 83 | @Override 84 | public void addContentView(View view, ViewGroup.LayoutParams params) { 85 | getDelegate().addContentView(view, params); 86 | } 87 | 88 | @Override 89 | protected void onPostResume() { 90 | super.onPostResume(); 91 | getDelegate().onPostResume(); 92 | } 93 | 94 | @Override 95 | protected void onTitleChanged(CharSequence title, int color) { 96 | super.onTitleChanged(title, color); 97 | getDelegate().setTitle(title); 98 | } 99 | 100 | @Override 101 | public void onConfigurationChanged(Configuration newConfig) { 102 | super.onConfigurationChanged(newConfig); 103 | getDelegate().onConfigurationChanged(newConfig); 104 | } 105 | 106 | @Override 107 | protected void onStop() { 108 | super.onStop(); 109 | getDelegate().onStop(); 110 | } 111 | 112 | @Override 113 | protected void onDestroy() { 114 | super.onDestroy(); 115 | getDelegate().onDestroy(); 116 | } 117 | 118 | public void invalidateOptionsMenu() { 119 | getDelegate().invalidateOptionsMenu(); 120 | } 121 | 122 | private AppCompatDelegate getDelegate() { 123 | if (mDelegate == null) { 124 | mDelegate = AppCompatDelegate.create(this, null); 125 | } 126 | return mDelegate; 127 | } 128 | } -------------------------------------------------------------------------------- /app/src/main/java/org/aeon/aeondaemon/app/Fragments/LogSlideFragment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 enerc 3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.aeon.aeondaemon.app.Fragments; 17 | 18 | import android.app.AlertDialog; 19 | import android.content.ClipData; 20 | import android.content.ClipboardManager; 21 | import android.content.Context; 22 | import android.content.DialogInterface; 23 | import android.os.Build; 24 | import android.os.Bundle; 25 | import android.os.Handler; 26 | import android.support.v4.app.Fragment; 27 | import android.text.Selection; 28 | import android.text.SpannableString; 29 | import android.text.method.ScrollingMovementMethod; 30 | import android.view.LayoutInflater; 31 | import android.view.View; 32 | import android.view.ViewGroup; 33 | import android.widget.ScrollView; 34 | import android.widget.TextView; 35 | 36 | import org.aeon.aeondaemon.app.MainActivity; 37 | import org.aeon.aeondaemon.app.R; 38 | import org.aeon.aeondaemon.app.model.Launcher; 39 | import org.aeon.aeondaemon.app.model.SynchronizeThread; 40 | 41 | public class LogSlideFragment extends Fragment { 42 | private static final String TAG = LogSlideFragment.class.getSimpleName(); 43 | private static long RefreshInterval = 1000; 44 | private ViewGroup rootView; 45 | private Context context = null; 46 | 47 | @Override 48 | public void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | 51 | final Handler handler = new Handler(); 52 | 53 | Runnable r = new Runnable() { 54 | @Override 55 | public void run() { 56 | ScrollView s = (ScrollView) rootView.findViewById(R.id.logs_scrollview); 57 | 58 | boolean hasFocus = MainActivity.getmViewPager().getCurrentItem() == MainActivity.FRAGMENT_LOG; 59 | if (hasFocus) { 60 | Launcher launcher = SynchronizeThread.getLauncher(); 61 | if (launcher == null) { 62 | TextView v = (TextView) rootView.findViewById(R.id.logs); 63 | v.setText(getString(R.string.daemon_not_running)); 64 | } else { 65 | launcher.updateStatus(); 66 | 67 | /** 68 | * Autoscrolling text 69 | * https://stackoverflow.com/questions/3506696/auto-scrolling-textview-in-android-to-bring-text-into-view 70 | */ 71 | TextView v = (TextView) rootView.findViewById(R.id.logs); 72 | v.setMovementMethod(new ScrollingMovementMethod()); 73 | 74 | SpannableString spannable = new SpannableString(launcher.getLogs()); 75 | Selection.setSelection(spannable, spannable.length()); 76 | v.setText(spannable, TextView.BufferType.SPANNABLE); 77 | s.fullScroll(ScrollView.FOCUS_DOWN); 78 | } 79 | } 80 | handler.postDelayed(this, RefreshInterval); 81 | } 82 | }; 83 | handler.postDelayed(r, RefreshInterval); 84 | } 85 | 86 | @Override 87 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 88 | rootView = (ViewGroup) inflater.inflate(R.layout.log_fragment, container, false); 89 | ((TextView) rootView.findViewById(R.id.logs)).setOnLongClickListener(copyListener); 90 | 91 | return rootView; 92 | } 93 | 94 | @Override 95 | public void onViewCreated(View view, Bundle savedInstanceState) { 96 | super.onViewCreated(view, savedInstanceState); 97 | } 98 | 99 | 100 | private View.OnLongClickListener copyListener = new View.OnLongClickListener() { 101 | public boolean onLongClick(View v) { 102 | ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 103 | ClipData clip = ClipData.newPlainText("label", ((TextView) v).getText()); 104 | clipboard.setPrimaryClip(clip); 105 | 106 | AlertDialog.Builder builder; 107 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 108 | builder = new AlertDialog.Builder(context, android.R.style.Theme_Material_Dialog_Alert); 109 | } else { 110 | builder = new AlertDialog.Builder(context); 111 | } 112 | builder.setTitle("Clipboard") 113 | .setMessage(R.string.logs_selected_msg) 114 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 115 | public void onClick(DialogInterface dialog, int which) { 116 | } 117 | }) 118 | .setIcon(android.R.drawable.ic_dialog_alert) 119 | .show(); 120 | return true; 121 | } 122 | }; 123 | 124 | @Override 125 | public void onAttach(Context _context) { 126 | super.onAttach(_context); 127 | context = _context; 128 | } 129 | } -------------------------------------------------------------------------------- /app/src/main/java/org/aeon/aeondaemon/app/Fragments/MainSlideFragment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 enerc 3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.aeon.aeondaemon.app.Fragments; 17 | 18 | import static org.aeon.aeondaemon.app.model.CollectPreferences.collectedPreferences; 19 | import static java.lang.Integer.parseInt; 20 | 21 | import android.app.Activity; 22 | import android.app.AlertDialog; 23 | import android.content.Context; 24 | import android.content.DialogInterface; 25 | import android.content.SharedPreferences; 26 | import android.net.wifi.WifiManager; 27 | import android.os.Build; 28 | import android.os.Bundle; 29 | import android.os.Handler; 30 | import android.preference.PreferenceManager; 31 | import android.support.v4.app.Fragment; 32 | import android.support.v4.content.ContextCompat; 33 | import android.support.v7.widget.SwitchCompat; 34 | import android.text.format.Formatter; 35 | import android.util.Log; 36 | import android.view.LayoutInflater; 37 | import android.view.View; 38 | import android.view.ViewGroup; 39 | import android.widget.TextView; 40 | 41 | import org.aeon.aeondaemon.app.MainActivity; 42 | import org.aeon.aeondaemon.app.R; 43 | import org.aeon.aeondaemon.app.model.CollectPreferences; 44 | import org.aeon.aeondaemon.app.model.Launcher; 45 | import org.aeon.aeondaemon.app.model.SynchronizeThread; 46 | 47 | import java.io.File; 48 | 49 | public class MainSlideFragment extends Fragment { 50 | private static final String TAG = MainSlideFragment.class.getSimpleName(); 51 | private static long RefreshInterval = 1000; 52 | public static String execError = null; 53 | private ViewGroup rootView; 54 | private Context context = null; 55 | private static boolean hasCriticalError = false; 56 | private static SharedPreferences sharedPreferences; 57 | private static SwitchCompat nodeSwitch; 58 | 59 | 60 | @Override 61 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 62 | rootView = (ViewGroup) inflater.inflate(R.layout.main_fragment, container, false); 63 | context = getActivity(); 64 | setDisconnectedValues(); 65 | 66 | final Handler handler = new Handler(); 67 | 68 | Runnable r = new Runnable() { 69 | @Override 70 | public void run() { 71 | doUpdate(); 72 | handler.postDelayed( this, RefreshInterval ); 73 | } 74 | }; 75 | handler.postDelayed(r,RefreshInterval); 76 | 77 | TextView v = (TextView) rootView.findViewById(R.id.sync_status); 78 | v.setText(getActivity().getString(R.string.daemon_not_running)); 79 | 80 | /** 81 | * On init, set switch to saved setting or use default 82 | * 83 | * on update, check node setting, update node status, update saved setting 84 | * 85 | */ 86 | nodeSwitch = (SwitchCompat) rootView.findViewById(R.id.enable_node); 87 | sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); 88 | initEnableNode(); 89 | 90 | return rootView; 91 | } 92 | 93 | /** 94 | * Sets the enable_node Switch with the last saved state of the button 95 | * defaults to false 96 | * and actually enables the node if previous state was true 97 | */ 98 | private void initEnableNode() { 99 | Boolean t = sharedPreferences.getBoolean("enable_node", false); 100 | SwitchCompat nodeSwitch = (SwitchCompat) rootView.findViewById(R.id.enable_node); 101 | nodeSwitch.setChecked(t.booleanValue()); 102 | collectedPreferences.setEnableNode(t.booleanValue()); 103 | } 104 | 105 | /** 106 | * Saves the toggle state of the button to be loaded on app restart 107 | * and actually turns on the node 108 | */ 109 | private void updateEnableNode() { 110 | collectedPreferences.setEnableNode(nodeSwitch.isChecked()); 111 | SharedPreferences.Editor editor = sharedPreferences.edit(); 112 | editor.putBoolean("enable_node", nodeSwitch.isChecked()); 113 | editor.commit(); 114 | } 115 | 116 | private void doUpdate() { 117 | boolean hasFocus = MainActivity.getmViewPager().getCurrentItem() == MainActivity.FRAGMENT_MAIN; 118 | if (hasFocus) { 119 | if (hasCriticalError) { 120 | Log.d(TAG,"hasCriticalError"); 121 | return; 122 | } 123 | if (execError != null) { 124 | if(!((Activity) context).isFinishing()) { 125 | AlertDialog.Builder builder; 126 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 127 | builder = new AlertDialog.Builder(context, android.R.style.Theme_Material_Dialog_Alert); 128 | } else { 129 | builder = new AlertDialog.Builder(context); 130 | } 131 | builder.setTitle("monerod") 132 | .setMessage(execError) 133 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 134 | public void onClick(DialogInterface dialog, int which) { 135 | } 136 | }) 137 | .setIcon(android.R.drawable.ic_dialog_alert) 138 | .show(); 139 | } 140 | execError = null; 141 | hasCriticalError = true; 142 | } 143 | 144 | updateEnableNode(); 145 | 146 | 147 | Launcher launcher = SynchronizeThread.getLauncher(); 148 | 149 | if (launcher == null) { 150 | TextView v = (TextView) rootView.findViewById(R.id.sync_status); 151 | v.setText(R.string.daemon_not_running); 152 | }; 153 | 154 | if (launcher.isStarting()) { 155 | TextView v = (TextView) rootView.findViewById(R.id.sync_status); 156 | v.setText(context.getString(R.string.sync_starting)); 157 | } else if (launcher.isAlive()) { 158 | TextView v = (TextView) rootView.findViewById(R.id.heightValue); 159 | String height = launcher.getHeight(); 160 | if (height != null) { 161 | v.setText("Height: " + height); 162 | } 163 | 164 | v = (TextView) rootView.findViewById(R.id.heightTarget); 165 | String targetHeight = launcher.getTarget(); 166 | 167 | if (targetHeight != null) { 168 | v.setText("Target Height: " + targetHeight); 169 | } 170 | 171 | v = (TextView) rootView.findViewById(R.id.syncPercentage); 172 | if (launcher.getSyncPercentage() != null) { 173 | v.setText("Sync Progress: " + launcher.getSyncPercentage() + "%"); 174 | } 175 | 176 | v = (TextView) rootView.findViewById(R.id.compiledMsgAeonVersion); 177 | if (launcher.getVersion() != null) v.setText(launcher.getVersion()); 178 | 179 | v = (TextView) rootView.findViewById(R.id.peers); 180 | if (launcher.getPeers() != null) 181 | v.setText(launcher.getPeers() + " " + context.getString(R.string.msg_peers_connected)); 182 | 183 | v = (TextView) rootView.findViewById(R.id.downloading); 184 | if (launcher.getDownloading() != null) 185 | v.setText(context.getString(R.string.download_at) + " " + launcher.getDownloading() + " kB/s"); 186 | 187 | v = (TextView) rootView.findViewById(R.id.disk); 188 | String s = String.format("%.1f", getUsedSpace()); 189 | v.setText(s + " " + context.getString(R.string.disk_used)); 190 | 191 | v = (TextView) rootView.findViewById(R.id.sync_status); 192 | v.setText(R.string.daemon_running); 193 | 194 | v = (TextView) rootView.findViewById(R.id.local_ip_address); 195 | int port = collectedPreferences.getRpcBindPort(); 196 | v.setText("Node Address: "+getLocalIpAddress()+(port > 0 ? (":"+port) : ":18081")); 197 | 198 | } else { 199 | // Unset all Main page values by default 200 | setDisconnectedValues(); 201 | } 202 | } 203 | } 204 | 205 | private void setDisconnectedValues() { 206 | TextView v = (TextView) rootView.findViewById(R.id.heightValue); 207 | v.setText(""); 208 | 209 | v = (TextView) rootView.findViewById(R.id.heightTarget); 210 | v.setText(""); 211 | 212 | v = (TextView) rootView.findViewById(R.id.syncPercentage); 213 | v.setText(""); 214 | 215 | v = (TextView) rootView.findViewById(R.id.compiledMsgAeonVersion); 216 | v.setText(""); 217 | 218 | v = (TextView) rootView.findViewById(R.id.peers); 219 | v.setText(""); 220 | 221 | v = (TextView) rootView.findViewById(R.id.downloading); 222 | v.setText(""); 223 | 224 | v = (TextView) rootView.findViewById(R.id.disk); 225 | v.setText(""); 226 | 227 | v = (TextView) rootView.findViewById(R.id.sync_status); 228 | v.setText(R.string.daemon_not_running); 229 | 230 | v = (TextView) rootView.findViewById(R.id.local_ip_address); 231 | v.setText(""); 232 | } 233 | 234 | private String getLocalIpAddress() { 235 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 236 | String ipAddress = Formatter.formatIpAddress(wifiManager.getConnectionInfo().getIpAddress()); 237 | return ipAddress; 238 | } 239 | 240 | /** 241 | * Get availabe free space on the disk 242 | * 243 | * @return percentage of free space. 244 | */ 245 | private float getUsedSpace() { 246 | File f = new File(collectedPreferences.isUseSDCard() ? collectedPreferences.getSdCardPath() : MainActivity.BINARY_PATH); 247 | return f.getFreeSpace() / 1024.0f / 1024.0f / 1024.0f; 248 | } 249 | 250 | @Override 251 | public void onResume(){ 252 | super.onResume(); 253 | } 254 | 255 | @Override 256 | public void onAttach(Context _context) { 257 | super.onAttach(_context); 258 | context = _context; 259 | } 260 | 261 | public static void setHasCriticalError(boolean _hasCriticalError) { 262 | hasCriticalError = _hasCriticalError; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /app/src/main/java/org/aeon/aeondaemon/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 enerc 3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.aeon.aeondaemon.app; 17 | 18 | import android.app.AlertDialog; 19 | import android.content.Context; 20 | import android.content.DialogInterface; 21 | import android.content.Intent; 22 | import android.content.SharedPreferences; 23 | import android.content.res.Resources; 24 | import android.graphics.drawable.ColorDrawable; 25 | import android.os.Bundle; 26 | import android.os.PowerManager; 27 | import android.preference.PreferenceManager; 28 | import android.support.v4.app.FragmentManager; 29 | import android.support.v4.app.FragmentPagerAdapter; 30 | import android.support.v4.content.ContextCompat; 31 | import android.support.v4.view.ViewPager; 32 | import android.support.v7.app.ActionBar; 33 | import android.support.v7.app.AppCompatActivity; 34 | import android.util.Log; 35 | import android.view.Menu; 36 | import android.view.MenuItem; 37 | import android.os.Build; 38 | 39 | import org.aeon.aeondaemon.app.Fragments.LogSlideFragment; 40 | import org.aeon.aeondaemon.app.Fragments.MainSlideFragment; 41 | import org.aeon.aeondaemon.app.model.SynchronizeThread; 42 | 43 | import java.io.File; 44 | import java.io.FileOutputStream; 45 | import java.io.IOException; 46 | import java.io.InputStream; 47 | import java.io.OutputStream; 48 | 49 | public class MainActivity extends AppCompatActivity { 50 | private static final String TAG = MainActivity.class.getSimpleName(); 51 | public static String BINARY_PATH = null; 52 | public static String PACKAGE_NAME; 53 | public static int FRAGMENT_MAIN=0; 54 | public static int FRAGMENT_LOG=1; 55 | private static AppCompatPreferenceActivity logActivity; 56 | private static AppCompatPreferenceActivity aboutActivity; 57 | private static AppCompatPreferenceActivity themeActivity; 58 | private static boolean initDone = false; 59 | private static ViewPager mViewPager; 60 | private SectionsPagerAdapter mSectionsPagerAdapter; 61 | private SynchronizeThread synchronizeThread = null; 62 | private static Context context = null; 63 | private PowerManager.WakeLock wakelock; 64 | 65 | @Override 66 | protected void onCreate(Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | 69 | PACKAGE_NAME = getApplicationContext().getPackageName(); 70 | context = getApplicationContext(); 71 | 72 | // if not initialized - because onCreate is called on screen rotation. 73 | if (!initDone) copyBinaryFile(); 74 | 75 | setContentView(R.layout.activity_main); 76 | 77 | // Create the adapter that will return a fragment for each of the three primary sections of the activity. 78 | mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); 79 | 80 | // Set up the ViewPager with the sections adapter. 81 | mViewPager = (ViewPager) findViewById(R.id.container); 82 | mViewPager.setAdapter(mSectionsPagerAdapter); 83 | 84 | // Create the background synchronization thread 85 | if (synchronizeThread == null) { 86 | synchronizeThread = new SynchronizeThread(context); 87 | Thread t = new Thread(synchronizeThread); 88 | t.start(); 89 | } 90 | 91 | // Acquire wakelock 92 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 93 | wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "pocketnode:wakelock"); 94 | wakelock.acquire(); 95 | 96 | initDone = true; 97 | } 98 | 99 | @Override 100 | public boolean onCreateOptionsMenu(Menu menu) { 101 | getMenuInflater().inflate(R.menu.menu_main, menu); 102 | return true; 103 | } 104 | 105 | @Override 106 | public void onResume(){ 107 | super.onResume(); 108 | getSupportActionBar().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.newColorPrimary))); 109 | 110 | 111 | setTheme(MainActivity.getStyle(context)); 112 | } 113 | 114 | @Override 115 | public boolean onOptionsItemSelected(MenuItem item) { 116 | int id = item.getItemId(); 117 | 118 | if (id == R.id.action_settings) { 119 | // launch settings activity 120 | startActivity(new Intent(MainActivity.this, SettingsPrefActivity.class)); 121 | return true; 122 | } 123 | if (id == R.id.action_about) { 124 | // launch about activity 125 | startActivity(new Intent(MainActivity.this, AboutActivity.class)); 126 | return true; 127 | } 128 | 129 | return super.onOptionsItemSelected(item); 130 | } 131 | 132 | 133 | /** 134 | * Copy the aeond binary file to a location where wa have execute rights 135 | */ 136 | private void copyBinaryFile() { 137 | Resources res = getResources(); 138 | //Log.d(TAG, " " + is64bitsProcessor()); 139 | 140 | InputStream in_s = res.openRawResource(is64bitsProcessor() ? R.raw.monerod64 : R.raw.monerod32); 141 | try { 142 | // read wownerod binary file from the ressource raw folder 143 | byte[] b = new byte[in_s.available()]; 144 | in_s.read(b); 145 | String pathName = context.getApplicationInfo().dataDir + "/lib"; 146 | 147 | BINARY_PATH = context.getCacheDir().getPath() + "/../monerod"; 148 | 149 | // write the file to an android executable location 150 | OutputStream outputStream = new FileOutputStream(BINARY_PATH); 151 | outputStream.write(b); 152 | outputStream.flush(); 153 | outputStream.close(); 154 | 155 | // make the file executable 156 | File f = new File(BINARY_PATH); 157 | f.setExecutable(true); 158 | 159 | } catch (IOException e) { 160 | Log.e(TAG, e.getMessage()); 161 | AlertDialog.Builder builder; 162 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 163 | builder = new AlertDialog.Builder(MainActivity.this, android.R.style.Theme_Material_Dialog_Alert); 164 | } else { 165 | builder = new AlertDialog.Builder(MainActivity.this); 166 | } 167 | builder.setTitle("Copy binary file") 168 | .setMessage(e.getMessage()) 169 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 170 | public void onClick(DialogInterface dialog, int which) { 171 | } 172 | }) 173 | .setIcon(android.R.drawable.ic_dialog_alert) 174 | .show(); 175 | 176 | } 177 | } 178 | 179 | 180 | public static void setAboutActivity(AppCompatPreferenceActivity _aboutActivity) { 181 | aboutActivity = _aboutActivity; 182 | } 183 | 184 | private boolean is64bitsProcessor() { 185 | String supported[] = Build.SUPPORTED_ABIS; 186 | for (String s : supported) { 187 | if (s.equals("arm64-v8a")) return true; 188 | } 189 | return false; 190 | } 191 | 192 | /** 193 | * A {@link FragmentPagerAdapter} that returns a fragment corresponding to 194 | * one of the sections/tabs/pages. 195 | */ 196 | public class SectionsPagerAdapter extends FragmentPagerAdapter { 197 | 198 | public SectionsPagerAdapter(FragmentManager fm) { 199 | super(fm); 200 | } 201 | 202 | @Override 203 | public android.support.v4.app.Fragment getItem(int position) { 204 | // getItem is called to instantiate the fragment for the given page. 205 | if (position == FRAGMENT_MAIN) return new MainSlideFragment(); 206 | else if (position == FRAGMENT_LOG) return new LogSlideFragment(); 207 | else return null; 208 | } 209 | 210 | @Override 211 | public int getCount() { 212 | return 2; 213 | } 214 | } 215 | 216 | public static ViewPager getmViewPager() { 217 | return mViewPager; 218 | } 219 | 220 | public static void setThemeActivity(AppCompatPreferenceActivity themeActivity) { 221 | MainActivity.themeActivity = themeActivity; 222 | } 223 | 224 | public static int getStyle(Context context) { 225 | return R.style.AppTheme; 226 | } 227 | 228 | 229 | public static Context getContext() { 230 | return context; 231 | } 232 | 233 | @Override 234 | protected void onDestroy() { 235 | // Clear wakelock 236 | wakelock.release(); 237 | super.onDestroy(); 238 | } 239 | 240 | } -------------------------------------------------------------------------------- /app/src/main/java/org/aeon/aeondaemon/app/SettingsPrefActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 enerc 3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.aeon.aeondaemon.app; 17 | 18 | import android.Manifest; 19 | import android.app.Activity; 20 | import android.app.AlertDialog; 21 | import android.content.Context; 22 | import android.content.DialogInterface; 23 | import android.content.SharedPreferences; 24 | import android.content.pm.PackageManager; 25 | import android.graphics.Color; 26 | import android.graphics.drawable.ColorDrawable; 27 | import android.os.Build; 28 | import android.os.Bundle; 29 | import android.preference.PreferenceFragment; 30 | import android.preference.PreferenceManager; 31 | import android.support.v4.app.ActivityCompat; 32 | import android.support.v4.content.ContextCompat; 33 | import android.util.Log; 34 | import android.view.MenuItem; 35 | 36 | import org.aeon.aeondaemon.app.Fragments.MainSlideFragment; 37 | import org.aeon.aeondaemon.app.model.CollectPreferences; 38 | import org.aeon.aeondaemon.app.model.Launcher; 39 | import org.aeon.aeondaemon.app.model.SynchronizeThread; 40 | 41 | import java.io.File; 42 | 43 | public class SettingsPrefActivity extends AppCompatPreferenceActivity { 44 | private static final String TAG = SettingsPrefActivity.class.getSimpleName(); 45 | private SharedPreferences.OnSharedPreferenceChangeListener listener; 46 | private Context context = null; 47 | private Activity activity = null; 48 | 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | 53 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 54 | context = getApplicationContext(); 55 | activity = this; 56 | 57 | 58 | // load settings fragment 59 | setContentView(R.layout.activity_settings); 60 | getFragmentManager().beginTransaction().replace(android.R.id.content, new MainPreferenceFragment()).commit(); 61 | 62 | listener = new SharedPreferences.OnSharedPreferenceChangeListener() { 63 | @Override 64 | public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 65 | Launcher launcher = SynchronizeThread.getLauncher(); 66 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 67 | 68 | // If SD card enabled set a preference if empty 69 | String path = preferences.getString("sd_storage", ""); 70 | boolean useSD = preferences.getBoolean("use_sd_card", false); 71 | // Card has been inserted and "Use SD card" checked 72 | if (useSD && path.equals("")) { 73 | SharedPreferences.Editor editor = preferences.edit(); 74 | if (CollectPreferences.getExternalStoragePath(context) != null) 75 | editor.putString("sd_storage", CollectPreferences.getExternalStoragePath(context)); 76 | else 77 | editor.putBoolean("use_sd_card", false); 78 | editor.commit(); 79 | 80 | getFragmentManager().beginTransaction().replace(android.R.id.content, new MainPreferenceFragment()).commit(); 81 | 82 | } 83 | 84 | // If custom location set a preference if empty 85 | path = preferences.getString("sd_custom_storage", ""); 86 | boolean useCustom = preferences.getBoolean("use_custom_storage", false); 87 | // Card has been inserted and "Use SD card" checked 88 | if (useCustom && path.equals("")) { 89 | 90 | SharedPreferences.Editor editor = preferences.edit(); 91 | editor.putString("sd_custom_storage", CollectPreferences.getCustomPath()); 92 | editor.commit(); 93 | 94 | getFragmentManager().beginTransaction().replace(android.R.id.content, new MainPreferenceFragment()).commit(); 95 | 96 | } 97 | 98 | // Android 6+ 99 | if (useCustom || useSD ) { 100 | boolean hasPermission = (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED); 101 | if (!hasPermission) { 102 | ActivityCompat.requestPermissions(activity, 103 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 104 | 1); 105 | } 106 | } 107 | 108 | if (useSD && useCustom) { 109 | AlertDialog.Builder builder; 110 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 111 | builder = new AlertDialog.Builder(SettingsPrefActivity.this, android.R.style.Theme_Material_Dialog_Alert); 112 | } else { 113 | builder = new AlertDialog.Builder(SettingsPrefActivity.this); 114 | } 115 | builder.setTitle(R.string.sd_custom_error) 116 | .setMessage(R.string.sd_custom_error_msg) 117 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 118 | public void onClick(DialogInterface dialog, int which) { 119 | } 120 | }) 121 | .setIcon(android.R.drawable.ic_dialog_alert) 122 | .show(); 123 | 124 | SharedPreferences.Editor editor = preferences.edit(); 125 | editor.putBoolean("use_custom_storage", false); 126 | editor.commit(); 127 | 128 | getFragmentManager().beginTransaction().replace(android.R.id.content, new MainPreferenceFragment()).commit(); 129 | } 130 | MainSlideFragment.setHasCriticalError(false); 131 | if (launcher != null) { 132 | Log.e(TAG, "Stop Wownero daemon"); 133 | launcher.exit(); 134 | } 135 | } 136 | }; 137 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 138 | preferences.registerOnSharedPreferenceChangeListener(listener); 139 | 140 | String sdLocation = CollectPreferences.getExternalStoragePath(context); 141 | // if the SD card has been removed. 142 | if (sdLocation == null) { 143 | SharedPreferences.Editor editor = preferences.edit(); 144 | editor.putBoolean("use_sd_card", false); 145 | editor.commit(); 146 | } 147 | setTheme(MainActivity.getStyle(getApplicationContext())); 148 | 149 | // TODO: Remove/check this 150 | getWindow().setBackgroundDrawable(new ColorDrawable(Color.WHITE)); 151 | } 152 | 153 | public static class MainPreferenceFragment extends PreferenceFragment { 154 | @Override 155 | public void onCreate(final Bundle savedInstanceState) { 156 | super.onCreate(savedInstanceState); 157 | addPreferencesFromResource(R.xml.pref_settings); 158 | } 159 | } 160 | 161 | @Override 162 | public boolean onOptionsItemSelected(MenuItem item) { 163 | if (item.getItemId() == android.R.id.home) { 164 | onBackPressed(); 165 | } 166 | return super.onOptionsItemSelected(item); 167 | } 168 | 169 | @Override 170 | public void onResume() { 171 | super.onResume(); 172 | 173 | setTheme(R.style.AppTheme); 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /app/src/main/java/org/aeon/aeondaemon/app/ThemeActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 enerc 3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.aeon.aeondaemon.app; 17 | 18 | import android.content.Context; 19 | import android.content.SharedPreferences; 20 | import android.graphics.drawable.ColorDrawable; 21 | import android.os.Bundle; 22 | import android.preference.PreferenceFragment; 23 | import android.support.v4.content.ContextCompat; 24 | import android.support.v7.app.ActionBar; 25 | import android.util.Log; 26 | import android.view.MenuItem; 27 | 28 | public class ThemeActivity extends AppCompatPreferenceActivity { 29 | private static final String TAG = ThemeActivity.class.getSimpleName(); 30 | private Context context = null; 31 | private SharedPreferences.OnSharedPreferenceChangeListener listener; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | context = getApplicationContext(); 37 | 38 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 39 | 40 | // load settings fragment 41 | getFragmentManager().beginTransaction().replace(android.R.id.content, new ThemeActivity.MainPreferenceFragment()).commit(); 42 | 43 | setContentView(R.layout.activity_theme); 44 | MainActivity.setThemeActivity(this); 45 | setTheme(MainActivity.getStyle(context)); 46 | } 47 | 48 | @Override 49 | public boolean onOptionsItemSelected(MenuItem item) { 50 | if (item.getItemId() == android.R.id.home) { 51 | onBackPressed(); 52 | } 53 | return super.onOptionsItemSelected(item); 54 | } 55 | 56 | public static class MainPreferenceFragment extends PreferenceFragment { 57 | @Override 58 | public void onCreate(final Bundle savedInstanceState) { 59 | super.onCreate(savedInstanceState); 60 | addPreferencesFromResource(R.xml.pref_theme); 61 | } 62 | } 63 | 64 | @Override 65 | public void onResume(){ 66 | super.onResume(); 67 | setTheme(MainActivity.getStyle(context)); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/org/aeon/aeondaemon/app/model/CollectPreferences.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 enerc 3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.aeon.aeondaemon.app.model;
17 |
18 | import static android.os.Environment.getExternalStoragePublicDirectory;
19 |
20 | import android.content.Context;
21 | import android.content.SharedPreferences;
22 | import android.os.Build;
23 | import android.os.Environment;
24 | import android.support.v4.content.ContextCompat;
25 | import android.util.Log;
26 |
27 | import java.io.File;
28 | import java.io.FileNotFoundException;
29 | import java.io.FileOutputStream;
30 | import java.util.Map;
31 |
32 | public class CollectPreferences {
33 | public static Settings collectedPreferences = new Settings();
34 | private static final String TAG = CollectPreferences.class.getSimpleName();
35 |
36 | public static void collect(SharedPreferences prefs) {
37 | Map
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.aeon.aeondaemon.app.model;
18 |
19 | import android.util.Log;
20 |
21 | import org.aeon.aeondaemon.app.Fragments.MainSlideFragment;
22 | import org.aeon.aeondaemon.app.MainActivity;
23 |
24 | import java.io.BufferedReader;
25 | import java.io.BufferedWriter;
26 | import java.io.File;
27 | import java.io.IOException;
28 | import java.io.InputStreamReader;
29 | import java.io.OutputStreamWriter;
30 | import java.lang.reflect.Field;
31 | import java.util.ArrayList;
32 | import java.util.Map;
33 |
34 | public class Launcher {
35 | private static final String TAG = Launcher.class.getSimpleName();
36 | private static int MAX_LOG_SIZE = 30000;
37 | private enum ProcessState { STOPPED, STARTING, RUNNING, STOPPING };
38 |
39 | private BufferedReader reader=null;
40 | private BufferedWriter writer=null;
41 | private String version=null;
42 | private StringBuffer logs=null;
43 | private String height;
44 | private String target;
45 | private String syncPercentage;
46 | private String peers;
47 | private String downloading;
48 | private Process process=null;
49 | private ProcessState processState = ProcessState.STOPPED;
50 |
51 | public String start(Settings pref) {
52 | if (!checkSDCard(pref)) {
53 | return "Fail to create "+pref.getSdCardPath();
54 | }
55 | MainSlideFragment.setHasCriticalError(false);
56 | try {
57 | // reset log buffer
58 | if (logs != null) logs.delete(0,logs.length());
59 |
60 | File f = new File(MainActivity.BINARY_PATH);
61 |
62 | String env = getEnv(pref);
63 | Log.e(TAG , env);
64 |
65 | // Executes the command.
66 | process = Runtime.getRuntime().exec(MainActivity.BINARY_PATH+" "+env);
67 |
68 | try {
69 | Field field = process.getClass().getDeclaredField("pid");
70 | field.setAccessible(true);
71 | } catch (Throwable e) {
72 | Log.e(TAG,e.getMessage());
73 | }
74 |
75 | // maps stdout
76 | reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
77 |
78 | // maps stdin
79 | writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
80 |
81 | } catch (IOException e) {
82 | Log.e(TAG,e.getMessage());
83 | return e.getMessage();
84 | }
85 | processState = ProcessState.STARTING;
86 |
87 | return null;
88 | }
89 |
90 | /**
91 | * Collect the logs, get the version string and get current sync status
92 | *
93 | * Non blocking I/O
94 | */
95 | public void updateStatus() {
96 | try {
97 | //Log.d(TAG,"updateStatus "+reader.ready());
98 | if (reader != null && reader.ready()) {
99 | char[] buffer = new char[16384];
100 | if (logs == null) logs = new StringBuffer();
101 |
102 | while (reader.ready()) {
103 | int read = reader.read(buffer);
104 | if (read > 0) {
105 | logs.append(buffer, 0, read);
106 | }
107 | }
108 |
109 | // truncate if log too big
110 | if (logs.length() > MAX_LOG_SIZE)
111 | logs.delete(0,logs.length() - MAX_LOG_SIZE);
112 |
113 | // Try to get monerod version and build number
114 | if (version == null) {
115 | int i = 0; //logs.toString().indexOf("I Wownero");
116 | //if (i != -1) {
117 | int j = logs.toString().substring(i).indexOf("Monero '");
118 | if (j != -1) {
119 | int k = logs.toString().substring(i+j).indexOf(")");
120 | version = logs.toString().substring(i+j,i+j+k+1);
121 | }
122 | //}
123 | }
124 |
125 | // Get Sync Percentage
126 | int s = logs.toString().lastIndexOf("%,");
127 | if (s > 0) {
128 | String syncReversed = "";
129 | s--;
130 | while (logs.charAt(s) != '(') {
131 | syncReversed += logs.charAt(s);
132 | s--;
133 | }
134 | syncPercentage = new StringBuilder(syncReversed).reverse().toString();
135 | }
136 |
137 | // Update Height and target
138 | int i = logs.toString().lastIndexOf("Height:");
139 | if (i > 0) {
140 | height = "";
141 | target = "";
142 | while (logs.charAt(i) != ' ') i++;
143 | while (logs.charAt(i) != ',') {
144 | height += logs.charAt(i);
145 | i++;
146 | }
147 | i += 2;
148 | while (logs.charAt(i) != ' ') i++;
149 | i++;
150 | while (logs.charAt(i) != ' ') {
151 | target += logs.charAt(i);
152 | i++;
153 | }
154 | }
155 |
156 | // Download speed
157 | i = logs.toString().lastIndexOf("Downloading at ");
158 | if (i > 0) {
159 | downloading = "";
160 | i += 15;
161 | while (logs.charAt(i) != ' ') {
162 | downloading += logs.charAt(i);
163 | i++;
164 | }
165 | i++;
166 | }
167 |
168 | // Number of peers
169 | i = logs.toString().lastIndexOf(" peers\n");
170 | if (i > 0) {
171 | i --;
172 | peers = "";
173 | while (logs.charAt(i) >= '0' && logs.charAt(i) <= '9') i--;
174 | i++;
175 | while (logs.charAt(i) != ' ') {
176 | peers += logs.charAt(i);
177 | i++;
178 | }
179 | }
180 | }
181 |
182 | } catch (IOException e) {
183 | Log.e(TAG,e.getMessage());
184 | }
185 | }
186 |
187 | public void getSyncInfo() {
188 | try {
189 | // if process not already terminated
190 | if (writer != null) {
191 | writer.write("sync_info\n");
192 | writer.flush();
193 | }
194 | } catch (IOException e) {
195 | Log.e(TAG,"getSyncInfo: " +e.getMessage());
196 | }
197 | }
198 |
199 | public void exit() {
200 | // sending an exit twice might make it waiting forever
201 | if (processState == ProcessState.STOPPING)
202 | return;
203 |
204 | try {
205 | if (writer != null) {
206 | writer.write("exit\n");
207 | writer.flush();
208 | }
209 | } catch (IOException e) {
210 | Log.e(TAG,e.getMessage());
211 | }
212 | processState = ProcessState.STOPPING;
213 | }
214 |
215 | public String getEnv(Settings pref) {
216 | ArrayList
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.aeon.aeondaemon.app.model;
17 |
18 | public class Settings {
19 | private boolean enableNode =false;
20 | private String dataDir=null;
21 | private String logFile="/dev/null";
22 | private int logLevel=0;
23 | private Boolean isTestnet = false;
24 | private Boolean isStageNet = false;
25 | private int blockSyncSize = 0;
26 | private int zmqRpcPort=0;
27 | private int p2pBindPort=0;
28 | private int rpcBindPort=0;
29 | private String addExclusiveNode=null;
30 | private String addPriorityNode=null;
31 | private String adress="Wmsmmjtzk269mpmWm9CTC8DXDs9FZmKdrbFqm1gAmdFxJwEtsZU9PxDJDLYxtLsoSSjn6y6iXYcXVfgYSAGC5vrL13rDqUs4n";
32 | private String seedNode=null;
33 | private String peerNode=null;
34 | private int outPeers=-1;
35 | private int inPeers=-1;
36 | private int limitRateUp=-1;
37 | private int limitRateDown=-1;
38 | private int limitRate=-1;
39 | private String boostrapDaemonAdress=null;
40 | private String boostrapDaemonLogin=null;
41 | private Boolean restrictedRpc=true;
42 | private String sdCardPath=null;
43 | private boolean useSDCard=false;
44 | private String customStoragePath=null;
45 | private boolean useCustomStorage=false;
46 | private boolean fastBlocSync=false;
47 |
48 | private boolean usePruning=true;
49 |
50 | public boolean isUseSDCard() {
51 | return useSDCard;
52 | }
53 |
54 | public void setUseSDCard(boolean useSDCard) {
55 | this.useSDCard = useSDCard;
56 | }
57 |
58 | public void setSdCardPath(String sdCardPath) {
59 | this.sdCardPath = sdCardPath;
60 | }
61 |
62 | public void setTestnet(Boolean testnet) {
63 | isTestnet = testnet;
64 | }
65 |
66 | public void setStageNet(Boolean stageNet) {
67 | isStageNet = stageNet;
68 | }
69 |
70 |
71 | public String getSdCardPath() {
72 | return sdCardPath;
73 | }
74 |
75 | public String getDataDir() {
76 | return dataDir;
77 | }
78 | public void setDataDir(String dataDir) {
79 | this.dataDir = dataDir;
80 | }
81 | public String getLogFile() {
82 | return logFile;
83 | }
84 | public void setLogFile(String logFile) {
85 | this.logFile = logFile;
86 | }
87 | public Boolean getIsTestnet() {
88 | return isTestnet;
89 | }
90 | public void setIsTestnet(Boolean isTestnet) {
91 | this.isTestnet = isTestnet;
92 | }
93 | public Boolean getIsStageNet() {
94 | return isStageNet;
95 | }
96 | public void setIsStageNet(Boolean isStageNet) {
97 | this.isStageNet = isStageNet;
98 | }
99 | public int getBlockSyncSize() {
100 | return blockSyncSize;
101 | }
102 | public void setBlockSyncSize(int blockSyncSize) {
103 | this.blockSyncSize = blockSyncSize;
104 | }
105 | public int getZmqRpcPort() {
106 | return zmqRpcPort;
107 | }
108 | public void setZmqRpcPort(int zmqRpcPort) {
109 | this.zmqRpcPort = zmqRpcPort;
110 | }
111 | public int getP2pBindPort() {
112 | return p2pBindPort;
113 | }
114 | public void setP2pBindPort(int p2pBindPort) {
115 | this.p2pBindPort = p2pBindPort;
116 | }
117 | public int getRpcBindPort() {
118 | return rpcBindPort;
119 | }
120 | public void setRpcBindPort(int rpcBindPort) {
121 | this.rpcBindPort = rpcBindPort;
122 | }
123 | public String getAddExclusiveNode() {
124 | return addExclusiveNode;
125 | }
126 | public void setAddExclusiveNode(String addExclusiveNode) {
127 | this.addExclusiveNode = addExclusiveNode;
128 | }
129 | public String getAdress() {
130 | return adress;
131 | }
132 | public void setAdress(String adress) {
133 | this.adress = adress;
134 | }
135 | public String getSeedNode() {
136 | return seedNode;
137 | }
138 | public void setSeedNode(String seedNode) {
139 | this.seedNode = seedNode;
140 | }
141 | public int getOutPeers() {
142 | return outPeers;
143 | }
144 | public void setOutPeers(int outPeers) {
145 | this.outPeers = outPeers;
146 | }
147 | public int getInPeers() {
148 | return inPeers;
149 | }
150 | public void setInPeers(int inPeers) {
151 | this.inPeers = inPeers;
152 | }
153 | public int getLimitRateUp() {
154 | return limitRateUp;
155 | }
156 | public void setLimitRateUp(int limitRateUp) {
157 | this.limitRateUp = limitRateUp;
158 | }
159 | public int getLimitRateDown() {
160 | return limitRateDown;
161 | }
162 | public void setLimitRateDown(int limitRateDown) {
163 | this.limitRateDown = limitRateDown;
164 | }
165 | public int getLimitRate() {
166 | return limitRate;
167 | }
168 | public void setLimitRate(int limitRate) {
169 | this.limitRate = limitRate;
170 | }
171 | public String getBoostrapDaemonAdress() {
172 | return boostrapDaemonAdress;
173 | }
174 | public void setBoostrapDaemonAdress(String boostrapDaemonAdress) {
175 | this.boostrapDaemonAdress = boostrapDaemonAdress;
176 | }
177 | public String getBoostrapDaemonLogin() {
178 | return boostrapDaemonLogin;
179 | }
180 | public void setBoostrapDaemonLogin(String boostrapDaemonLogin) {
181 | this.boostrapDaemonLogin = boostrapDaemonLogin;
182 | }
183 | public String getPeerNode() {
184 | return peerNode;
185 | }
186 | public void setPeerNode(String peerNode) {
187 | this.peerNode = peerNode;
188 | }
189 | public String getAddPriorityNode() {
190 | return addPriorityNode;
191 | }
192 | public void setAddPriorityNode(String addPriorityNode) {
193 | this.addPriorityNode = addPriorityNode;
194 | }
195 | public Boolean getRestrictedRpc() {
196 | return restrictedRpc;
197 | }
198 | public void setRestrictedRpc(Boolean restrictedRpc) {
199 | this.restrictedRpc = restrictedRpc;
200 | }
201 |
202 | public int getLogLevel() {
203 | return logLevel;
204 | }
205 |
206 | public Boolean getTestnet() {
207 | return isTestnet;
208 | }
209 |
210 | public Boolean getStageNet() {
211 | return isStageNet;
212 | }
213 |
214 | public void setLogLevel(int logLevel) {
215 | this.logLevel = logLevel;
216 | }
217 |
218 | public boolean getIsEnableNode() { return enableNode; }
219 |
220 | public void setEnableNode(boolean enableNode) { this.enableNode = enableNode; }
221 |
222 | public boolean isFastBlocSync() {
223 | return fastBlocSync;
224 | }
225 |
226 | public void setFastBlocSync(boolean fastBlocSync) {
227 | this.fastBlocSync = fastBlocSync;
228 | }
229 |
230 | public String getCustomStoragePath() {
231 | return customStoragePath;
232 | }
233 |
234 | public void setCustomStoragePath(String customStoragePath) {
235 | this.customStoragePath = customStoragePath;
236 | }
237 |
238 | public boolean isUseCustomStorage() {
239 | return useCustomStorage;
240 | }
241 |
242 | public void setUseCustomStorage(boolean useCustomStorage) {
243 | this.useCustomStorage = useCustomStorage;
244 | }
245 |
246 |
247 | public boolean usePruning() {
248 | return usePruning;
249 | }
250 |
251 | public void setUsePruning(boolean usePruning) {
252 | this.usePruning = usePruning;
253 | }
254 |
255 | }
256 |
--------------------------------------------------------------------------------
/app/src/main/java/org/aeon/aeondaemon/app/model/SynchronizeThread.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 enerc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.aeon.aeondaemon.app.model;
17 |
18 | import android.content.Context;
19 | import android.content.SharedPreferences;
20 | import android.preference.PreferenceManager;
21 | import android.util.Log;
22 |
23 | import org.aeon.aeondaemon.app.Fragments.MainSlideFragment;
24 |
25 | public class SynchronizeThread implements Runnable {
26 | private static final String TAG = SynchronizeThread.class.getSimpleName();
27 | private static Launcher launcher = null;
28 | private Context context;
29 | private static long RefreshInterval = 1000;
30 | private int counter = 0;
31 | private static int SendSyncCmd = 10;
32 |
33 | public SynchronizeThread(Context _context) {
34 | this.context = _context;
35 | }
36 |
37 | @Override
38 | public void run() {
39 | while (true) {
40 | /**
41 | * TODO: Review the logic here- this is where all the magic happens.
42 | *
43 | * - We always want to keep track of UI preference changes
44 | * - If we enable the node from the UI, we want to start
45 | * - If we disable the node from the UI, we want to stop
46 | *
47 | * - If the node stops, we want to update the UI to reflect that there are no
48 | * peers/connections, that it's not syncing, etc. rather than display the last known vals
49 | *
50 | */
51 | boolean nodeEnabled = CollectPreferences.collectedPreferences.getIsEnableNode();
52 |
53 | if (launcher == null) {
54 | launcher = new Launcher();
55 | updatePreferences();
56 | }
57 | if (launcher.isStopped() && nodeEnabled) {
58 | // Restart the background process
59 | updatePreferences(); // properties may have been changed in the settings.
60 | String status = launcher.start(CollectPreferences.collectedPreferences);
61 | if (status != null) {
62 | launcher.updateStatus();
63 | String msg = "";
64 | if (launcher.getLogs().length() > 0) msg = launcher.getLogs();
65 | else msg = "monerod process failed to start. err=" + status;
66 | MainSlideFragment.execError = msg;
67 | }
68 | } else if (!nodeEnabled) {
69 | if (!launcher.isStopped()) {
70 | launcher.exit();
71 | }
72 | updatePreferences(); // properties may have been changed in the settings.
73 | launcher.updateStatus();
74 | } else if (launcher.isAlive()) {
75 | if (counter >= SendSyncCmd) {
76 | launcher.getSyncInfo();
77 | counter = 0;
78 | }
79 | launcher.updateStatus();
80 | //Log.e(TAG,"launcher.getSyncInfo");
81 | }
82 |
83 | try {
84 | Thread.sleep(RefreshInterval);
85 | } catch (InterruptedException e) {
86 | Log.e(TAG, e.getMessage());
87 | }
88 | counter ++;
89 | }
90 | }
91 |
92 | private void updatePreferences() {
93 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
94 | CollectPreferences.collect(preferences);
95 |
96 | // If using internal storage, set the path for it.
97 | if (CollectPreferences.collectedPreferences.isUseSDCard()) {
98 | CollectPreferences.collectedPreferences.setDataDir(CollectPreferences.collectedPreferences.getSdCardPath());
99 | } else if (CollectPreferences.collectedPreferences.isUseCustomStorage()) {
100 | CollectPreferences.collectedPreferences.setDataDir(CollectPreferences.collectedPreferences.getCustomStoragePath());
101 | } else {
102 | String storagePath = context.getFilesDir().getPath();
103 | CollectPreferences.collectedPreferences.setDataDir(storagePath);
104 | }
105 | }
106 |
107 | public static Launcher getLauncher() {
108 | return launcher;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/actionbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CryptoGrampy/xmr-pocket-node/1a4b77d7b4f7e69a87bf24874936ba915cf26b44/app/src/main/res/drawable/actionbar.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CryptoGrampy/xmr-pocket-node/1a4b77d7b4f7e69a87bf24874936ba915cf26b44/app/src/main/res/drawable/background.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/coins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CryptoGrampy/xmr-pocket-node/1a4b77d7b4f7e69a87bf24874936ba915cf26b44/app/src/main/res/drawable/coins.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/coinscolor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CryptoGrampy/xmr-pocket-node/1a4b77d7b4f7e69a87bf24874936ba915cf26b44/app/src/main/res/drawable/coinscolor.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/mic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CryptoGrampy/xmr-pocket-node/1a4b77d7b4f7e69a87bf24874936ba915cf26b44/app/src/main/res/drawable/mic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/mythbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CryptoGrampy/xmr-pocket-node/1a4b77d7b4f7e69a87bf24874936ba915cf26b44/app/src/main/res/drawable/mythbar.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/mythology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CryptoGrampy/xmr-pocket-node/1a4b77d7b4f7e69a87bf24874936ba915cf26b44/app/src/main/res/drawable/mythology.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/oval_selected.xml:
--------------------------------------------------------------------------------
1 |
2 |