Tips For Making Secured Android Apps
Android apps are becoming a necessity in this world. From business, companies, education, collaboration, personal blogs, foods and groceries, health and medicine, social media platforms, accessing Government Services and Digital payments and even voting everything is available on the Internet. But how to make our Android apps secure? So in this article, we are going to provide some tips to make your Android apps secure.
1. How to Check When Someone Is Installing the app such that the Device is rooted or not
First, you should know what is android rooting so by default app comes with certain limitations by the manufacturer of the app and operating system and rooting allows users to overcome these restrictions and get some extra control and customizable options and you can check in your Android app if someone is installing the app the user device is rooted or not. If rooted then you can just finish the activity. To know more about Rooting: What is Android Rooting?
Java
package com.example.gfgapp; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); boolean isRooted = checkRoot(); if (isRooted) { Toast.makeText( this , "Your device is rooted" , Toast.LENGTH_LONG).show(); // Finishing the activity if root is detected new Handler().postDelayed(() -> finish(), 2000 ); } } private boolean checkRoot() { return checkRootMethod1() || checkRootMethod2() || checkRootMethod3(); } private boolean checkRootMethod1() { String[] paths = { "/system/app/Superuser.apk" , "/system/app/SuperSU.apk" }; for (String path : paths) { File file = new File(path); if (file.exists()) { return true ; } } return false ; } private boolean checkRootMethod2() { try { Process process = Runtime.getRuntime().exec( new String[]{ "which" , "su" }); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream())); String line = reader.readLine(); if (line != null ) { return true ; } } catch (IOException e) { showToast( "Exception occurred: " + e.getMessage()); } return false ; } private boolean checkRootMethod3() { String buildTags = Build.TAGS; return buildTags != null && buildTags.contains( "test-keys" ); } private void showToast(String message) { Toast.makeText( this , message, Toast.LENGTH_LONG).show(); } } |
Kotlin
package com.example.gfgapp import android.os.Build import android.os.Bundle import android.os.Handler import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import java.io.BufferedReader import java.io.File import java.io.IOException import java.io.InputStreamReader class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val isRooted = checkRoot() if (isRooted) { Toast.makeText( this , "Your device is rooted" , Toast.LENGTH_LONG).show() // Finishing the activity if root is detected Handler().postDelayed({ finish() }, 2000 ) } } private fun checkRoot(): Boolean { return checkRootMethod1() || checkRootMethod2() || checkRootMethod3() } private fun checkRootMethod1(): Boolean { val paths = arrayOf( "/system/app/Superuser.apk" , "/system/app/SuperSU.apk" ) for (path in paths) { val file = File(path) if (file.exists()) { return true } } return false } private fun checkRootMethod2(): Boolean { try { val process = Runtime.getRuntime().exec(arrayOf( "which" , "su" )) val reader = BufferedReader(InputStreamReader(process.inputStream)) val line = reader.readLine() if (line != null ) { return true } } catch (e: IOException) { showToast( "Exception occurred: " + e.message) } return false } private fun checkRootMethod3(): Boolean { val buildTags = Build.TAGS return buildTags != null && buildTags.contains( "test-keys" ) } private fun showToast(message: String) { Toast.makeText( this , message, Toast.LENGTH_LONG).show() } } |
Code Explanation:
- checkRootMethod1(): This method checks for the known related root files (‘Superuser.apk’,’SuperSu.apk’) in the system apps directory that is /system/app/
- checkRootMethod2() This method uses the Runtime.getRuntime().exec() method to execute the command that su in the device shell. It then reads the output of the command and checks if it is not null. If the output is not null it means that the su command exists and indicates that the device is rooted.
Note: The command su refers to the superuser. The su command is typically associated with gaining root or superuser privileges on an Android device.
- checkRootMethod3() This method checks for the presence of the test-keys string in the Build.TAGS field. The Build.TAGS field is a system property that contains information about the device build and the test-keys string indicates that the device build was signed with test keys which is a common practice in the process of rooting Android devices.
2. Checking User Is Using VPN To Access The App
There are many things that users can do when he/she is trying to access our app by connecting to VPN such as IP Address Masking, Network latency, Fake user current location, etc.. to prevent this in your app.
Java
package com.example.gfgapp; import androidx.appcompat.app.AppCompatActivity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private NetworkChangeReceiver networkChangeReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Creating an instance of the NetworkChangeReceiver // to Monitor Network changes constantly networkChangeReceiver = new NetworkChangeReceiver(); // Registering the receiver to listen for connectivity change broadcasts IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(networkChangeReceiver, filter); } @Override protected void onDestroy() { super .onDestroy(); // Unregister the receiver to avoid memory leaks unregisterReceiver(networkChangeReceiver); } // BroadcastReceiver for monitoring network changes private class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // Checking if the broadcast action is CONNECTIVITY_ACTION if (action != null && action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); // Get the network capabilities of the active network NetworkCapabilities nc = cm.getNetworkCapabilities(cm.getActiveNetwork()); // Check if the active network has VPN transport capability // That is it is connected to vpn if (nc != null && nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { Toast.makeText(context, "Please don't use VPN" , Toast.LENGTH_LONG).show(); // Finishing the activity finish(); } } } } } |
Kotlin
package com.example.gfgapp import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { private lateinit var networkChangeReceiver: NetworkChangeReceiver override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Creating an instance of the NetworkChangeReceiver // to Monitor Network changes constantly networkChangeReceiver = NetworkChangeReceiver() // Registering the receiver to listen for connectivity change broadcasts val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) registerReceiver(networkChangeReceiver, filter) } override fun onDestroy() { super .onDestroy() // Unregister the receiver to avoid memory leaks unregisterReceiver(networkChangeReceiver) } // BroadcastReceiver for monitoring network changes private inner class NetworkChangeReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val action = intent.action // Checking if the broadcast action is CONNECTIVITY_ACTION if (action != null && action == ConnectivityManager.CONNECTIVITY_ACTION) { val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager // Get the network capabilities of the active network val nc = cm.getNetworkCapabilities(cm.activeNetwork) // Check if the active network has VPN transport // capability (i.e., it is connected to a VPN) if (nc != null && nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { Toast.makeText(context, "Please don't use VPN" , Toast.LENGTH_LONG).show() // Finishing the activity finish() } } } } } |
In this code, we are constantly checking if the user is connected to VPN or not if connected to VPN then it automatically app stops running giving one
Output:
If connected to VPN:
3. Hiding All The Content Of The App When There Is No Internet
The first major importance of hiding the app content when there is no internet is that it prevents unauthorized access, protects user privacy, reduces vulnerabilities, and enhances the user experience. By hiding the content sensitive information remains inaccessible, minimizing the risk of data breaches and unauthorized access. It also reduces the app’s exposure to potential security threats. So we have created a basic activity_main.xml for demonstration purposes and used frame layout so that all the things that are present in the frame layout will be hidden when there is no internet you can add as many things inside the frame layout and we are using LottieAnimation to display the Animation when there is no internet To Know More or To Implement the Lottie animation.
activity_main.xml:
XML
<? xml version = "1.0" encoding = "utf-8" ?> < androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:app = "http://schemas.android.com/apk/res-auto" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "match_parent" tools:context = ".MainActivity" > < FrameLayout android:id = "@+id/main_layout" android:layout_width = "match_parent" android:layout_height = "match_parent" > < ImageView android:id = "@+id/imageView" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_gravity = "center" android:src = "@drawable/gfg" android:layout_marginBottom = "17dp" app:layout_constraintEnd_toEndOf = "parent" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintTop_toTopOf = "parent" app:layout_constraintBottom_toTopOf = "@+id/linearLayout" app:layout_constraintVertical_bias = "0.5" /> < LinearLayout android:id = "@+id/linearLayout" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:orientation = "vertical" app:layout_constraintTop_toBottomOf = "@+id/imageView" app:layout_constraintBottom_toBottomOf = "parent" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintEnd_toEndOf = "parent" > < TextView android:id = "@+id/textView" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_gravity = "center" android:text = "Welcome To GFG APP" android:textAllCaps = "true" android:textColor = "@color/black" android:textSize = "20sp" android:textStyle = "bold" /> </ LinearLayout > </ FrameLayout > < com.airbnb.lottie.LottieAnimationView android:id = "@+id/animation_view" android:layout_width = "match_parent" android:layout_height = "match_parent" android:visibility = "gone" app:lottie_autoPlay = "true" app:lottie_loop = "true" app:lottie_rawRes = "@raw/nc4" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintEnd_toEndOf = "parent" /> <!-- This raw will be your animation that will be displayed if there is no internet connection--> </ androidx.constraintlayout.widget.ConstraintLayout > |
MainActivity.Java:
Java
package com.example.gfgapp; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; import android.view.View; import android.widget.FrameLayout; import androidx.appcompat.app.AppCompatActivity; import com.airbnb.lottie.LottieAnimationView; public class MainActivity extends AppCompatActivity { private FrameLayout mainLayout; private LottieAnimationView animationView; private NetworkChangeReceiver networkChangeReceiver; @Override @SuppressWarnings ( "deprecation" ) protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); mainLayout = findViewById(R.id.main_layout); animationView = findViewById(R.id.animation_view); // Checking if the device is not connected to the internet if (!isConnected()) { // Hiding the main layout and show the lottie animation mainLayout.setVisibility(View.GONE); animationView.setVisibility(View.VISIBLE); animationView.setAnimation(R.raw.nc4); animationView.loop( true ); animationView.playAnimation(); } networkChangeReceiver = new NetworkChangeReceiver(); IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); // Registering the network change receiver to // listen for connectivity changes registerReceiver(networkChangeReceiver, intentFilter); } @Override protected void onDestroy() { super .onDestroy(); // Unregistering the network change receiver // when the activity is destroyed unregisterReceiver(networkChangeReceiver); } // Checking if the device is connected to the internet or not private boolean isConnected() { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); return networkInfo != null && networkInfo.isConnected(); } // BroadcastReceiver to listen for network connectivity changes private class NetworkChangeReceiver extends BroadcastReceiver { @Override @SuppressWarnings ( "deprecation" ) public void onReceive(Context context, Intent intent) { // Checking if the device is connected to the internet if (isConnected()) { // Showing the main layout and hiding the lottie animation Toast.makeText(MainActivity. this , "Connected To Internet" ,Toast.LENGTH_SHORT).show(); mainLayout.setVisibility(View.VISIBLE); animationView.setVisibility(View.GONE); animationView.cancelAnimation(); } else { // Hiding the main layout and showing the lottie animation Toast.makeText(MainActivity. this , "No Internet" ,Toast.LENGTH_SHORT).show(); mainLayout.setVisibility(View.GONE); animationView.setVisibility(View.VISIBLE); animationView.setAnimation(R.raw.nc4); animationView.loop( true ); animationView.playAnimation(); } } } } |
Output:
4. Checking For Developer Mode
When Developer Mode is Enabled User Can Modify The Code or Behavior of the app. This can include injecting malicious code, introducing vulnerabilities, or bypassing security measures present in the app. Developer mode allows users to grant additional permissions to apps beyond what is typically available in the app. This means an unauthorized user can grant excessive permissions to an app potentially compromising user privacy and security.
So Below Is the code to check If Developer Mode Is enabled In the Device then automatically The Activity Will Close With Displaying One Toast Message. Also Added one More Thing that will check for developer Mode in the interval of 5 seconds so that if the user Turn On The Developer Mode After Entering the app.
Basic activity_main.xml file:
XML
<? xml version = "1.0" encoding = "utf-8" ?> < androidx.constraintlayout.widget.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:app = "http://schemas.android.com/apk/res-auto" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "match_parent" android:background = "@color/white" tools:context = ".MainActivity" > < ImageView android:id = "@+id/gfgImage" android:layout_width = "130dp" android:layout_height = "130dp" android:contentDescription = "@string/app_name" android:src = "@drawable/gfg" app:layout_constraintBottom_toBottomOf = "parent" app:layout_constraintHorizontal_bias = "0.548" app:layout_constraintLeft_toLeftOf = "parent" app:layout_constraintRight_toRightOf = "parent" app:layout_constraintTop_toTopOf = "parent" app:layout_constraintVertical_bias = "0.355" /> < TextView android:id = "@+id/textView" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "Welcome To The App" android:textColor = "@color/black" android:textSize = "20sp" android:textStyle = "bold" app:layout_constraintBottom_toBottomOf = "parent" app:layout_constraintHorizontal_bias = "0.595" app:layout_constraintLeft_toLeftOf = "parent" app:layout_constraintRight_toRightOf = "parent" app:layout_constraintTop_toTopOf = "parent" app:layout_constraintVertical_bias = "0.602" /> </ androidx.constraintlayout.widget.ConstraintLayout > |
Working On MainActivity.java file also added Kotlin Code
Java
package com.ayush.gfgapp; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import android.widget.Toast; public class MainActivity extends AppCompatActivity { // You Can Specify Time Interval here in MiliSeconds // Checking every 5 seconds private static final long CHECK_INTERVAL = 5000 ; private Handler handler; private Runnable checkDeveloperModeRunnable; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler = new Handler(); checkDeveloperModeRunnable = new Runnable() { @Override public void run() { if (isDeveloperModeEnabled()) { Toast.makeText(MainActivity. this , "Turn Off Developer Mode" , Toast.LENGTH_SHORT).show(); finish(); // Closing the activity if Developer Mode Is Enabled in the Device } else { // Continuing checking in CHECK_INTERVAL milliseconds handler.postDelayed( this , CHECK_INTERVAL); } } }; // Starting checking for developer mode handler.post(checkDeveloperModeRunnable); } private boolean isDeveloperModeEnabled() { return android.provider.Settings.Secure.getInt( getContentResolver(), android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0 ) != 0 ; } @Override protected void onDestroy() { super .onDestroy(); // Removing the runnable from the handler to avoid memory leaks handler.removeCallbacks(checkDeveloperModeRunnable); } } |
Kotlin
package com.ayush.gfgapp import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.widget.Toast class MainActivity : AppCompatActivity() { private val CHECK_INTERVAL: Long = 5000 private lateinit var handler: Handler private lateinit var checkDeveloperModeRunnable: Runnable override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) handler = Handler() checkDeveloperModeRunnable = object : Runnable { override fun run() { if (isDeveloperModeEnabled()) { Toast.makeText( this @MainActivity , "Please disable Developer Mode" , Toast.LENGTH_SHORT).show() finish() // Closing the activity if Developer Mode is enabled on the device } else { // Continuing checking after CHECK_INTERVAL milliseconds handler.postDelayed( this , CHECK_INTERVAL) } } } // Start checking for developer mode handler.post(checkDeveloperModeRunnable) } private fun isDeveloperModeEnabled(): Boolean { return android.provider.Settings.Secure.getInt( contentResolver, android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0 ) != 0 } override fun onDestroy() { super .onDestroy() handler.removeCallbacks(checkDeveloperModeRunnable) } } |
Output:
- When Developer Mode Is Enabled By Default
- If Developer Mode is Disabled Then App Should Work Normally
- If Developer Mode Is Enabled After User Enters The app
Contact Us