diff --git a/.gitignore b/.gitignore index 77617a1..8cd4281 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .gradle -build/ +app/build/ # Ignore Gradle GUI config gradle-app.setting diff --git a/README.md b/README.md index 2c4783a..8758376 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # MinecraftPEServer An Android application to let you run PocketMine or Nukkit on your phone! + +# Uage +Some code from PocketMine-Android,but most of them have been rewrited! + +# Abort JRE +You can get "nukkit_library.tar.gz" from Here + diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..72f5c9d --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 21 + buildToolsVersion "21.1.0" + + defaultConfig { + applicationId "net.fengberd.minecraftpe_server" + minSdkVersion 9 + targetSdkVersion 21 + versionCode 3 + versionName "1.0.2" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.actionbarsherlock:actionbarsherlock:4.4.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..ebce3e3 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ac056ef --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/busybox b/app/src/main/assets/busybox new file mode 100644 index 0000000..7c88238 Binary files /dev/null and b/app/src/main/assets/busybox differ diff --git a/app/src/main/assets/php b/app/src/main/assets/php new file mode 100644 index 0000000..ee580ce Binary files /dev/null and b/app/src/main/assets/php differ diff --git a/app/src/main/java/net/fengberd/minecraftpe_server/HomeActivity.java b/app/src/main/java/net/fengberd/minecraftpe_server/HomeActivity.java new file mode 100644 index 0000000..3549ec3 --- /dev/null +++ b/app/src/main/java/net/fengberd/minecraftpe_server/HomeActivity.java @@ -0,0 +1,344 @@ +package net.fengberd.minecraftpe_server; + +import java.io.*; + +import android.os.*; +import android.app.*; +import android.view.*; +import android.widget.*; +import android.content.*; +import android.view.View.*; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.app.SherlockActivity; + +public class HomeActivity extends SherlockActivity +{ + final static int FORCE_CLOSE_CODE = 143; + final static int CONSOLE_CODE = FORCE_CLOSE_CODE + 1; + + public static Intent serverIntent=null; + public static HomeActivity homeActivity = null; + public static RadioButton radio_pocketmine=null,radio_nukkit=null; + public static CheckBox check_kusud=null,check_ansi=null; + public static Button button_start=null,button_stop=null,button_install_php=null,button_install_jre=null,button_mount=null; + + public static boolean isStarted = false,nukkitMode=false,ansiMode=false; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_home); + + homeActivity=this; + ServerUtils.setContext(homeActivity); + + button_stop=(Button) findViewById(R.id.button_stop); + button_start=(Button) findViewById(R.id.button_start); + button_mount=(Button) findViewById(R.id.button_mount); + button_install_php=(Button)findViewById(R.id.button_install_php); + button_install_jre=(Button)findViewById(R.id.button_install_jre); + + check_ansi=(CheckBox)findViewById(R.id.check_ansi); + check_kusud=(CheckBox)findViewById(R.id.check_kusud); + + radio_nukkit=(RadioButton) findViewById(R.id.radio_nukkit); + radio_pocketmine=(RadioButton) findViewById(R.id.radio_pocketmine); + + check_ansi.setChecked(ansiMode); + + radio_nukkit.setChecked(nukkitMode); + radio_pocketmine.setChecked(!nukkitMode); + + check_ansi.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + ansiMode=check_ansi.isChecked(); + } + }); + + radio_nukkit.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + nukkitMode=true; + refreshEnabled(); + } + }); + radio_pocketmine.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + nukkitMode=false; + refreshEnabled(); + } + }); + + button_mount.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + try + { + String binary="su",busybox=ServerUtils.getAppDirectory()+"/busybox "; + if(((CheckBox)findViewById(R.id.check_kusud)).isChecked()) + { + binary="ku.sud"; + } + Runtime.getRuntime().exec(binary+" -c "+busybox+"mount -o rw,remount /").waitFor(); + if(!new File("/lib").exists()) + { + Runtime.getRuntime().exec(binary+" -c "+busybox+"mkdir /lib").waitFor(); + } + else + { + Runtime.getRuntime().exec(binary+" -c "+busybox+"umount /lib").waitFor(); + } + Runtime.getRuntime().exec(binary+" -c "+busybox+"mount -o bind "+ServerUtils.getAppDirectory()+"/java/lib /lib").waitFor(); + toast("Done"); + } + catch(Exception e) + { + toast(e.toString()); + } + } + }); + button_install_jre.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + final ProgressDialog dialog=new ProgressDialog(homeActivity); + dialog.setCancelable(false); + dialog.setMessage(getString(R.string.message_installing)); + dialog.show(); + new Thread(new Runnable() + { + public void run() + { + try + { + File libData=new File(Environment.getExternalStorageDirectory().toString()+"/nukkit_library.tar.gz"); + if(!libData.exists()) + { + toast(getString(R.string.message_install_fail_path)+" "+Environment.getExternalStorageDirectory().toString()); + } + else + { + File inside=new File(ServerUtils.getAppDirectory()+"/java/nukkit_library.tar.gz"); + inside.delete(); + new File(ServerUtils.getAppDirectory()+"/java").mkdirs(); + OutputStream os=new FileOutputStream(inside); + InputStream is=new FileInputStream(libData); + int cou=0; + byte[] buffer=new byte[8192]; + while((cou=is.read(buffer))!=-1) + { + os.write(buffer,0,cou); + } + is.close(); + os.close(); + installBusybox(); + Runtime.getRuntime().exec("../busybox tar zxf nukkit_library.tar.gz",new String[0],new File(ServerUtils.getAppDirectory()+"/java")).waitFor(); + inside.delete(); + toast(R.string.message_install_success); + } + } + catch(Exception e) + { + toast(getString(R.string.message_install_fail)+"\n"+e.toString()); + } + runOnUiThread(new Runnable() + { + public void run() + { + dialog.dismiss(); + refreshEnabled(); + } + }); + } + }).start(); + } + }); + button_install_php.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + final ProgressDialog dialog=new ProgressDialog(homeActivity); + dialog.setCancelable(false); + dialog.setMessage(getString(R.string.message_installing)); + dialog.show(); + new Thread(new Runnable() + { + public void run() + { + try + { + installBusybox(); + copyAsset("php",ServerUtils.getAppDirectory()+"/php"); + toast(R.string.message_install_success); + } + catch(Exception e) + { + toast(getString(R.string.message_install_fail)+"\n"+e.toString()); + } + runOnUiThread(new Runnable() + { + public void run() + { + dialog.dismiss(); + refreshEnabled(); + } + }); + } + }).start(); + } + }); + button_start.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + isStarted=true; + refreshEnabled(); + serverIntent=new Intent(homeActivity,ServerService.class); + startService(serverIntent); + ServerUtils.runServer(); + } + }); + button_stop.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(ServerUtils.isRunning()) + { + ServerUtils.executeCMD("stop"); + } + } + }); + refreshEnabled(); + } + + public static void installBusybox() throws Exception + { + copyAsset("busybox",ServerUtils.getAppDirectory()+"/busybox"); + new File(ServerUtils.getAppDirectory()+"/busybox").setExecutable(true,true); + } + + public static void refreshEnabled() + { + radio_nukkit.setEnabled(!isStarted); + radio_pocketmine.setEnabled(!isStarted); + button_mount.setEnabled(!isStarted); + button_install_php.setEnabled(!isStarted); + button_install_jre.setEnabled(!isStarted); + if(nukkitMode && !new File(ServerUtils.getAppDirectory()+"/java/jre/bin/java").exists()) + { + button_start.setEnabled(false); + } + else if(!nukkitMode && !new File(ServerUtils.getAppDirectory()+"/php").exists()) + { + button_start.setEnabled(false); + } + else + { + button_start.setEnabled(!isStarted); + } + button_stop.setEnabled(isStarted); + } + + public static void copyAsset(String name,String target) throws Exception + { + File tmp=new File(target); + tmp.delete(); + OutputStream os=new FileOutputStream(tmp); + InputStream is=homeActivity.getAssets().open(name); + int cou=0; + byte[] buffer=new byte[8192]; + while((cou=is.read(buffer))!=-1) + { + os.write(buffer,0,cou); + } + is.close(); + os.close(); + } + + public static void stopNotifyService() + { + if(homeActivity != null && serverIntent != null) + { + homeActivity.runOnUiThread(new Runnable() + { + public void run() + { + isStarted=false; + refreshEnabled(); + homeActivity.stopService(serverIntent); + } + }); + } + } + + public void toast(int text) + { + toast(getString(text)); + } + + public void toast(final String text) + { + if(homeActivity!=null) + { + homeActivity.runOnUiThread(new Runnable() + { + public void run() + { + Toast.makeText(homeActivity,text,Toast.LENGTH_SHORT).show(); + } + }); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + menu.add(0,CONSOLE_CODE,0,getString(R.string.menu_console)) + .setIcon(R.drawable.hardware_dock) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.add(0,FORCE_CLOSE_CODE,0,getString(R.string.menu_kill)); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + if(item.getItemId() == android.R.id.home || item.getItemId() == 0) + { + return false; + } + if(item.getItemId() == FORCE_CLOSE_CODE) + { + ServerUtils.stopServer(); + if(serverIntent != null) + { + stopService(serverIntent); + } + isStarted=false; + refreshEnabled(); + } + else if(item.getItemId() == CONSOLE_CODE) + { + startActivity(new Intent(homeActivity,LogActivity.class)); + } + return true; + } +} + diff --git a/app/src/main/java/net/fengberd/minecraftpe_server/LogActivity.java b/app/src/main/java/net/fengberd/minecraftpe_server/LogActivity.java new file mode 100644 index 0000000..2592875 --- /dev/null +++ b/app/src/main/java/net/fengberd/minecraftpe_server/LogActivity.java @@ -0,0 +1,120 @@ +package net.fengberd.minecraftpe_server; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.app.SherlockActivity; + +import android.os.*; +import android.text.*; +import android.widget.*; +import android.view.View; +import android.view.View.OnClickListener; + +public class LogActivity extends SherlockActivity +{ + final static int CLEAR_CODE = 143; + final static int COPY_CODE = CLEAR_CODE + 1; + + public static LogActivity logActivity=null; + public static ScrollView sv; + public static SpannableStringBuilder currentLog = new SpannableStringBuilder(); + public static Button button_command=null; + public static TextView label_log=null; + public static EditText edit_command=null; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_log); + + logActivity=this; + label_log=(TextView) findViewById(R.id.label_log); + label_log.setText(currentLog); + edit_command=(EditText)findViewById(R.id.edit_command); + sv=(ScrollView) findViewById(R.id.logScrollView); + button_command = (Button)findViewById(R.id.button_send); + button_command.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View arg0) + { + log(">" + edit_command.getText()); + ServerUtils.executeCMD(edit_command.getText().toString()); + edit_command.setText(""); + } + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + if(item.getItemId() == CLEAR_CODE) + { + currentLog=new SpannableStringBuilder(); + label_log.setText(""); + return true; + } + else if(item.getItemId() == COPY_CODE) + { + ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + clipboard.setText(currentLog); + Toast.makeText(this,R.string.message_copied,Toast.LENGTH_SHORT).show(); + } + return false; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + menu.add(0,COPY_CODE,0,getString(R.string.menu_copy)) + .setIcon(R.drawable.content_copy) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.add(0,CLEAR_CODE,0,getString(R.string.menu_clear)) + .setIcon(R.drawable.content_discard) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + return true; + } + + public static void log(final String line) + { + final CharSequence result=HomeActivity.ansiMode?Html.fromHtml("" + line.replace("&","&") + .replace("<","<") + .replace(">",">") + .replace(" "," ") + .replace("\u001b[1m","") + .replace("\u001b[3m","") + .replace("\u001b[4m","") + .replace("\u001b[9m","") + .replace("\u001b[m","") + .replace("\u001b[38;5;16m","") + .replace("\u001b[38;5;19m","") + .replace("\u001b[38;5;34m","") + .replace("\u001b[38;5;37m","") + .replace("\u001b[38;5;124m","") + .replace("\u001b[38;5;127m","") + .replace("\u001b[38;5;214m","") + .replace("\u001b[38;5;145m","") + .replace("\u001b[38;5;59m","") + .replace("\u001b[38;5;63m","") + .replace("\u001b[38;5;83m","") + .replace("\u001b[38;5;87m","") + .replace("\u001b[38;5;203m","") + .replace("\u001b[38;5;207m","") + .replace("\u001b[38;5;227m","") + .replace("\u001b[38;5;231m","") + "
"):(line+"\n"); + currentLog.append(result); + if(logActivity != null) + { + logActivity.runOnUiThread(new Runnable() + { + public void run() + { + label_log.append(result); + sv.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + } + } +} + diff --git a/app/src/main/java/net/fengberd/minecraftpe_server/ServerService.java b/app/src/main/java/net/fengberd/minecraftpe_server/ServerService.java new file mode 100644 index 0000000..6a54c1c --- /dev/null +++ b/app/src/main/java/net/fengberd/minecraftpe_server/ServerService.java @@ -0,0 +1,55 @@ +package net.fengberd.minecraftpe_server; + +import android.os.*; +import android.app.*; +import android.content.*; + +public class ServerService extends Service +{ + private boolean isRunning = false; + + @Override + public int onStartCommand(Intent intent,int flags,int startId) + { + run(); + return START_NOT_STICKY; + } + + @Override + public void onDestroy() + { + stop(); + } + + @Override + public IBinder onBind(Intent intent) + { + return null; + } + + private void run() + { + if(!isRunning) + { + isRunning=true; + Context context = getApplicationContext(); + Notification note = new Notification(R.drawable.ic_launcher,(HomeActivity.nukkitMode?"Nukkit":"PocketMine")+" is running",System.currentTimeMillis()); + Intent i = new Intent(context,HomeActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + PendingIntent pi = PendingIntent.getActivity(this,0,i,0); + note.setLatestEventInfo(this,(HomeActivity.nukkitMode?"Nukkit":"PocketMine")+" "+HomeActivity.homeActivity.getString(R.string.message_running),HomeActivity.homeActivity.getString(R.string.message_tap_open),pi); + note.flags|=Notification.FLAG_NO_CLEAR; + startForeground(1337,note); + } + } + + private void stop() + { + if(isRunning) + { + isRunning=false; + stopForeground(true); + } + } +} + diff --git a/app/src/main/java/net/fengberd/minecraftpe_server/ServerUtils.java b/app/src/main/java/net/fengberd/minecraftpe_server/ServerUtils.java new file mode 100644 index 0000000..92b8c71 --- /dev/null +++ b/app/src/main/java/net/fengberd/minecraftpe_server/ServerUtils.java @@ -0,0 +1,249 @@ +package net.fengberd.minecraftpe_server; + +import java.io.*; +import java.nio.charset.Charset; + +import android.content.Context; + +public final class ServerUtils +{ + public static Context mContext; + + private static OutputStream stdin; + private static InputStream stdout; + + final public static void setContext(Context mContext) + { + ServerUtils.mContext=mContext; + } + + final public static String getAppDirectory() + { + return mContext.getApplicationInfo().dataDir; + } + + final public static String getDataDirectory() + { + String dir=android.os.Environment.getExternalStorageDirectory().getPath() + (HomeActivity.nukkitMode?"/Nukkit":"/PocketMine"); + new File(dir).mkdirs(); + return dir; + } + + final public static Boolean killProcessByName(String mProcessName) + { + return execCommand(getAppDirectory() + "/busybox killall -9 " + mProcessName); + } + + final public static void stopServer() + { + killProcessByName(HomeActivity.nukkitMode?"java":"php"); + } + + static Process serverProc; + + public static Boolean isRunning() + { + try + { + serverProc.exitValue(); + } + catch(Exception e) + { + return true; + } + return false; + } + + final public static void runServer() + { + File f = new File(getDataDirectory(),"/tmp"); + if(!f.exists()) + { + f.mkdir(); + } + else if(!f.isDirectory()) + { + f.delete(); + f.mkdir(); + } + setPermission(); + String file = "/Nukkit.jar"; + if(!HomeActivity.nukkitMode) + { + if(new File(getDataDirectory() + "/PocketMine-MP.phar").exists()) + { + file="/PocketMine-MP.phar"; + } + else + { + file = "/src/pocketmine/PocketMine.php"; + } + } + File ini=new File(getDataDirectory() + "/php.ini"); + if(!HomeActivity.nukkitMode && !ini.exists()) + { + try + { + ini.createNewFile(); + FileOutputStream os=new FileOutputStream(ini); + os.write("phar.readonly=0\nphar.require_hash=1\ndate.timezone=Asia/Shanghai\nshort_open_tag=0\nasp_tags=0\nopcache.enable=1\nopcache.enable_cli=1\nopcache.save_comments=1\nopcache.fast_shutdown=0\nopcache.max_accelerated_files=4096\nopcache.interned_strings_buffer=8\nopcache.memory_consumption=128\nopcache.optimization_level=0xffffffff".getBytes("UTF8")); + os.close(); + } + catch(Exception e) + { + + } + } + String[] args=null; + if(HomeActivity.nukkitMode) + { + args=new String[] + { + getAppDirectory() + "/java/jre/bin/java", + "-jar", + getDataDirectory() + file, + HomeActivity.ansiMode?"enable-ansi":"disable-ansi" + }; + } + else + { + args=new String[] + { + getAppDirectory() + "/php", + "-c", + getDataDirectory() + "/php.ini", + getDataDirectory() + file, + HomeActivity.ansiMode?"--enable-ansi":"--disable-ansi" + }; + } + ProcessBuilder builder = new ProcessBuilder(args); + builder.redirectErrorStream(true); + builder.directory(new File(getDataDirectory())); + builder.environment().put("TMPDIR",getDataDirectory() + "/tmp"); + try + { + serverProc=builder.start(); + stdout=serverProc.getInputStream(); + stdin=serverProc.getOutputStream(); + Thread tMonitor = new Thread() + { + public void run() + { + InputStreamReader reader = new InputStreamReader(stdout,Charset.forName("UTF-8")); + BufferedReader br = new BufferedReader(reader); + while(isRunning()) + { + try + { + char[] buffer = new char[8192]; + int size = 0; + while((size = br.read(buffer,0,buffer.length)) != -1) + { + StringBuilder s = new StringBuilder(); + for(int i = 0; i < size; i++) + { + char c = buffer[i]; + if(c == '\r') + { + continue; + } + if(c == '\n' || c == '\u0007') + { + String line = s.toString(); + if(c == '\u0007' && line.startsWith("\u001B]0;")) + { + //Do nothing. + } + else + { + LogActivity.log(line); + } + s=new StringBuilder(); + } + else + { + s.append(buffer[i]); + } + } + } + } + catch(Exception e) + { + e.printStackTrace(); + } + finally + { + try + { + br.close(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + LogActivity.log("[PE Server] Server was stopped."); + HomeActivity.stopNotifyService(); + } + }; + tMonitor.start(); + } + catch(Exception e) + { + LogActivity.log("[PE Server] Unable to start "+(HomeActivity.nukkitMode?"Java":"PHP")+"."); + LogActivity.log(e.toString()); + HomeActivity.stopNotifyService(); + killProcessByName(HomeActivity.nukkitMode?"java":"php"); + } + return; + } + + final public static boolean execCommand(String mCommand) + { + Runtime r = Runtime.getRuntime(); + try + { + r.exec(mCommand).waitFor(); + } + catch(Exception e) + { + r=null; + return false; + } + return true; + } + + final static public void setPermission() + { + try + { + if(HomeActivity.nukkitMode) + { + Runtime.getRuntime().exec("./busybox chmod -R 755 java",new String[0],new File(getAppDirectory())).waitFor(); + } + else + { + new File(getAppDirectory()+"/php").setExecutable(true,true); + } + } + catch(Exception e) + { + + } + } + + public static void executeCMD(String Cmd) + { + try + { + stdin.write((Cmd + "\r\n").getBytes()); + stdin.flush(); + } + catch(Exception e) + { + + } + } +} + diff --git a/app/src/main/res/drawable-hdpi/content_copy.png b/app/src/main/res/drawable-hdpi/content_copy.png new file mode 100644 index 0000000..72c6bc6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/content_copy.png differ diff --git a/app/src/main/res/drawable-hdpi/content_discard.png b/app/src/main/res/drawable-hdpi/content_discard.png new file mode 100644 index 0000000..ffd19d9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/content_discard.png differ diff --git a/app/src/main/res/drawable-hdpi/hardware_dock.png b/app/src/main/res/drawable-hdpi/hardware_dock.png new file mode 100644 index 0000000..37f3a93 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/hardware_dock.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..143b1a7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml new file mode 100644 index 0000000..435f242 --- /dev/null +++ b/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + +