Wednesday, 12 April 2017

Using HttpUrlConnection in Android

Background

Android has two options for doing network operations using Http clients -
  1. Apache HttpClient
    1. DefaultHttpClient
    2. AndroidHttpClient
  2. HttpUrlConnection


Back in 2011 Android developers announced that Android team is not actively working on Apache HTTP Client and that  new android applications should use HttpURLConnection and that is where they will be spending their energy going forward.

For more details you can read their blog -  Android’s HTTP Clients 

It's been a long way since they has posted that post and here we are. Android Http client was totally removed from Android 6.0 (M). Ofcourse you can manually plug it in. But android developer site says the following -

Android 6.0 release removes support for the Apache HTTP client. If your app is using this client and targets Android 2.3 (API level 9) or higher, use the HttpURLConnection class instead. This API is more efficient because it reduces network use through transparent compression and response caching, and minimizes power consumption. To continue using the Apache HTTP APIs, you must first declare the following compile-time dependency in your build.gradle file:

android {
    useLibrary 'org.apache.http.legacy'
}


But I would recommend don't do this unless some legacy API needs this. Move on to HttpUrlConenction. In this post we will see how we can use HttpUrlConnection for network operations in android.

Using HttpUrlConnection in Android

HttpURLConnection is a general-purpose, lightweight HTTP client suitable for most applications. A simple snippet would be- 

        URL url = new URL("http://google.com");
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("GET");
        try {
            InputStream in = new BufferedInputStream(urlConnection.getInputStream());
            readStream(in);
        } finally {
            urlConnection.disconnect();
        }

So you open a connection by calling openConnection() on an URL instance. You can then set the type of request using setRequestMethod(). It can be GET, POST etc. Then you read the stream by calling urlConnection.getInputStream(). Finally you call disconnect() to release your connection.


NOTE : By default HttpUrlConnection will use GET as request type i.e you dont have to mention  urlConnection.setRequestMethod("GET"); . For others as started above you can use - setRequestMethod();

NOTE :  Connection is actually established when you call getInputStream() or getOutputStream() or getResponseCode() on your httpUrlConenction instance.

NOTE : If your response is not 2** then it would be an error(You can read the response code by calling getResponseCode() method). In that case you need tor read error stream. You can do that by using getErrorStream() to read the error response. The headers can be read in the normal way using getHeaderFields().
 

How disconnect() works?

    Each HttpURLConnection instance is used to make a single request but the underlying network connection to the HTTP server may be transparently shared by other instances. Calling the close() methods on the InputStream or OutputStream of an HttpURLConnection after a request may free network resources associated with this instance but has no effect on any shared persistent connection. Calling the disconnect() method may close the underlying socket if a persistent connection is otherwise idle at that time.   

 -> So basically your HttpUrlConnection instance may get GCed but the underlying TCP socket will get pooled and reused unless you call disconnect() on it in which case it may or may not close the socket. So if you are not reconnecting to same URL it is safe to always call disconnect and let android handle the socket pooling and closing part.

Android docs clearly states -  Disconnecting releases the resources held by a connection so they may be closed or reused.

Handling request body

Sometime a request made to a server may need a request body to be supplied. For that you can use the httpUrlConnection instance's outputStream.

        URL url = new URL("http://test.com");
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("POST");
        urlConnection.setDoOutput(true);
        urlConnection.setChunkedStreamingMode(0);
        try {
            OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
            writeStream(out);

            InputStream in = new BufferedInputStream(urlConnection.getInputStream());
            readStream(in);
        } finally {
            urlConnection.disconnect();
        }

Make sure you set setDoOutput(true) if you have request body to be sent. Similarly set setDoInput(true) if you are reading back from the connenction. If you just want to see response code using - getResponseCode() method that these are not required.

NOTE : Notice how we have use BufferedStreams over input/output streams. They are used for performance reasons - so that data is buffered and we can easily read/write it.

Setting headers and connection timeouts

If you are familiar with HttpClient then you must have set connection and socket timeouts. In HttpUrlConenction you need to do following -

        urlConnection.setConnectTimeout(30000);
        urlConnection.setReadTimeout(30000);

These are in milliseconds. I have set it to half a minute above. To set headers in the request you can do -

urlConnection.setRequestProperty("headerName", "headerValue");

So lets say you want to set user-agent in your request you do -

urlConnection.setRequestProperty("User-Agent", "Mozilla");

Similarly you can enable disable redirects using -
urlConnection.setInstanceFollowRedirects(false);

Making secure connection (https)

If you are working with https (secure connection) protocol then your actual class would be - HttpsUrlConnection.

NOTE :  You can still use HttpUrlConnection since HttpUrlConenction extends HttpsURLConnection (by polymorphism).

HttpsUrlConnection will allow you to override HostnameVerifier and SSLSocketFactory.

Eg.

static {
    TrustManager[] trustAllCertificates = new TrustManager[] {
        new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null; // Not relevant.
            }
            @Override
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
                // Do nothing. Just allow them all.
            }
            @Override
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
                // Do nothing. Just allow them all.
            }
        }
    };

    HostnameVerifier trustAllHostnames = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true; // Just allow them all.
        }
    };

    try {
        System.setProperty("jsse.enableSNIExtension", "false");
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCertificates, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames);
    }
    catch (GeneralSecurityException e) {
        throw new ExceptionInInitializerError(e);
    }


Sample take from this SO discussion.

NOTE :   Calling HttpsURLConnection.setDefaultSSLSocketFactory() or HttpsURLConnection.setDefaultHostnameVerifier() will set it up for all Https connections. If you want to set it for specific connections then type cast it to HttpsUrlConenction instance and then call set methods on it.

Handling multipart/ form-data

 You'd normally use multipart/form-data encoding for mixed POST content (binary and character data). The encoding is in more detail described in RFC2388.

String param = "value";
File textFile = new File("/path/to/file.txt");
File binaryFile = new File("/path/to/file.bin");
String boundary = Long.toHexString(System.currentTimeMillis()); // Just generate some unique random value.
String CRLF = "\r\n"; // Line separator required by multipart/form-data.
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

try (
    OutputStream output = connection.getOutputStream();
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true);
) {
    // Send normal param.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF);
    writer.append(CRLF).append(param).append(CRLF).flush();

    // Send text file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"textFile\"; filename=\"" + textFile.getName() + "\"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF); // Text file itself must be saved in this charset!
    writer.append(CRLF).flush();
    Files.copy(textFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // Send binary file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"binaryFile\"; filename=\"" + binaryFile.getName() + "\"").append(CRLF);
    writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF);
    writer.append("Content-Transfer-Encoding: binary").append(CRLF);
    writer.append(CRLF).flush();
    Files.copy(binaryFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // End of multipart/form-data.
    writer.append("--" + boundary + "--").append(CRLF).flush();

Again code is taken from this SO discussion.

Other advantages of using HttpUrlConnection

  • HttpUrlConnection by default honors system proxy. However if you want to specify a custom proxy then you can do so with API - url.openConnection(proxy)
  • It also has transparent response compression. It will automatically add the header - Accept-Encoding: gzip to outgoing responses and also handle it automatically for incoming connections.
  •  It also attempts to connect with Server Name Indication (SNI) which allows multiple HTTPS hosts to share an IP address.

Related Links

Sunday, 2 April 2017

Android material design floating labels on EditText and Password visibility Toggle

Background

With android design support library new element - TextInputLayout was introduced. With this we can totally omit static labels that we put before text input elements (EditText for example). With this label will float on the top of the EditText when user focuses on that field for entering data. Also we can show error on the same field post validation. That gets show floating below the EditText. It also has passwordToggleEnabled   which lets you toggle the visibility of your password field with an eye icon (it can be customized to use a different icon too). We will see all of these in this post.

Android material design floating labels on EditText and Password visibility Toggle

Make sure your app level gradle has following dependencies added -

    compile 'com.android.support:appcompat-v7:25.1.1'
    compile 'com.android.support:design:25.3.1'

NOTE : Base version of you appcompat and your design library should be same. For eg. it's 25 for this case.

Next lets go straight to the layout. The feature that we are looking for goes something like this -

        <android.support.design.widget.TextInputLayout
            android:id="@+id/input_layout_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
 app:passwordToggleContentDescription="@string/passwordToggleContentDescription"
            app:passwordToggleEnabled="true"
            app:passwordToggleTint="@color/colorAccent">

            <EditText
                android:id="@+id/input_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/hint_password"
                android:inputType="textPassword"/>

        </android.support.design.widget.TextInputLayout>



Now I have combined both floating labels and password toggle in a single example. Lets see how it works now.

  • First of all you need to wrap  EditText element inside TextInputLayout element. 
  • Next android:hint value of EditText will come out on top as a floating label.
  • For password visibility to be toggle we need to provide app:passwordToggleEnabled="true" attribute. Others are optional really. Lets see what all attributes it supports.

  1.  passwordToggleEnabled : allows you choose whether or not you want the password visibility to be toggled.
  2. passwordToggleContentDescription :  allows a string to be set for the content description. This is used for accessibility feature.
  3. passwordToggleDrawable : allows one set a different icon other than the default visibility toggle icon (eye icon). 
  4. passwordToggleTint : allows the visibility toggle icon to be tinted to whatever colors you indicate.
  5. passwordToggleTintMode : sets the blending mode used to apply the background tint.
You can see the complete specs in android developers site. It would look like below -








Final screen is just showing a toast when you press login and all validations have gone though successfully.Speaking of validation lets come to it.

Do not worry about the minute code details like string resources. You can find the complete code on my github repo -
Lets just focus on concepts for now.

We said in our initial discussion that TextInputLayout can handle errors as well and they are shown floating just below the EditText. Lets look at the code for it.

  1. In your activity's onCreate() method get a reference to the TextInputLayout element and reference to the EditText wrapped in it.
  2. Next you can addTextChangedListener() to your EditText and do validations there.
  3. To set and display an error you simply need to call setError()on textInputLayout element. To remove error you can simply call setErrorEnabled(false).
Lets see how we can do it in case of our Email field. First of all get a reference to TextInputLayout and the EditeText in it -

        inputLayoutEmail = (TextInputLayout) findViewById(R.id.input_layout_email);

        inputEmail = (EditText) findViewById(R.id.input_email);


Next add addTextChangedListener to inputEmail and in it's afterTextChanged() method simply do your validations. In this case we are just calling a private validate method from afterTextChanged() method as follows -

    private boolean validateEmail(String text) {
        if(!isValidEmail(text)) {
            inputLayoutEmail.setError(getString(R.string.error_email));
            inputEmail.requestFocus();
            return false;
        }
        else {
            inputLayoutEmail.setErrorEnabled(false);
        }
        return true;
    }



    private boolean isValidEmail(String email) {
        return !TextUtils.isEmpty(email) && android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches();
    }


and you are done. You should see error as shown in following screenshots -




Again for complete code please refer to my github repo -

Related Links



Saturday, 1 April 2017

Android : URI is not registered (Settings | Project Settings | Schemas and DTDs)

Background

When you are developing android application you might sometime see following error on your layouts xml files in Android studio-
  • URI is not registered (Settings | Project Settings | Schemas and DTDs)


Everything seems correct except this error is persistent. Gradle sync works fine too. Lets see how we can resolve this.

Solution

  • Before we see how to resolve this issue make your layout file is in correct directory structure. It should be under layout folder which would be under res folder.




  • Now look at build variant tab at the bottom left of android Studio. You should see the current version set. It could be -
    • debug OR
    • release
In my case it was release. Just switch the version and then switch it back. In my case I switched it to release and then back to debug and the error was gone.


  •  If above 2 methods did not work out for you then Close all open tabs and reopen them. 
  • Last but not the least - restart Android Studio.
I know these are just silly workaround. Let me know which one works for you :)

 

Wednesday, 29 March 2017

CS 1.6 redirection to other server issue

Background

This is not a technical post rather a solution for the issue I faced while playing CS 1.6 online. I had bought it on Steam and happen to play around online on different servers. After a while it seemed to be redirecting to other servers and that seems to be issue. How can it connect to a server without Me telling it to? Well well see that in a moment.

I have steam installed and playing CS 1.6 on my mac. So path and files location may be different for you.

CS 1.6 redirection to other server issue

First of all find your counter strike installation directory. For me it is -
  • /Users/athakur/Library/Application Support/Steam/steamapps/common/Half-Life/cstrike
It would be different for you based on which OS you are using and how did you install it.

One you locate it try to find files with .cfg extension. The file we are interested in is 
  • config.cfg OR
  • userconfig.cfg  (If you have it)
In these files look for bind commands that ask to redirect to some IP. For eg. -
  • bind "F12" "Connect 93.119.24.96:27015";
When you connect to a rogue server it can alter these files to change your bindings and user configuration. You can see the permissions of these files. Your user would have write permissions on  it.



NOTE : Word of advice. Do not connect to servers that don't have folks playing in it and the ones you don't trust.

Now that we know what the issue is lets see how we can fix it.

Solution : CS 1.6 redirection to other server issue

  1. Close your CS game and delete the compromised config file.
  2. Restart you game and you can see that the config.cfg file is created again with default values. You can also go in game and click on reset to defaults. Do not connect to any servers.
  3. Once you have confirmed your file exist and you have made chances to it based on your configurations (binding keys etc) you need to make it read only.
  4. For this you can execute chmod ugo-w config.cfg command i.e you remove write permissions for the file. And your gamming life will be back to normal :)



NOTE : For me it was just config.cfg but if you open config.cfg it internally execute userconfig.cfg. So make sure if you have it, it is not compromised too.

 Related Links

Sunday, 19 March 2017

Creating a new Xposed module in Android

How Xposed works

Before we start lets see how Xposed works -

There is a process that is called "Zygote". This is the heart of the Android runtime. Every application is started as a copy ("fork") of it. This process is started by an /init.rc script when the phone is booted. The process start is done with /system/bin/app_process, which loads the needed classes and invokes the initialization methods.

This is where Xposed comes into play. When you install the framework, an extended app_process executable is copied to /system/bin. This extended startup process adds an additional jar to the classpath and calls methods from there at certain places. For instance, just after the VM has been created, even before the main method of Zygote has been called. And inside that method, we are part of Zygote and can act in its context.

The jar is located at /data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar and its source code can be found here. Looking at the class XposedBridge, you can see the main method. This is what I wrote about above, this gets called in the very beginning of the process. Some initializations are done there and also the modules are loaded.

Creating a new Xposed module in Android

I have couple of app on playstore . I am going to use FlashLight to demonstrate Xposed module.

  • Create a normal Android app. Add following extra meta data in the apps manifest file. This is how xposed installer app knows your apk is a xposed module.

        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <meta-data
            android:name="xposeddescription"
            android:value="Demo example that renders Flashlight app (com.osfg.flashlight) useless" />
        <meta-data
            android:name="xposedminversion"
            android:value="53" />

  • Next create a class that implements IXposedHookLoadPackage like below -

public class XPFlashLightKiller implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

        if (!loadPackageParam.packageName.equals("com.osfg.flashlight"))
            return;

        XposedBridge.log("Loaded app: " + loadPackageParam.packageName);

        XposedHelpers.findAndHookMethod("com.osfg.flashlight.FlashLightActivity", loadPackageParam.classLoader, "isFlashSupported", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                // this will be called before the clock was updated by the original method
            }
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                // this will be called after the clock was updated by the original method
                XposedBridge.log("Hooken method isFlashSupported of class com.osfg.flashlight.FlashLightActivity");
                param.setResult(false);
            }
        });
    }
}

NOTE :  Here we have used XC_MethodHook as callback so that we can execute methods before and after original method is executed. Other alternative is to replace the original method entirely - original hooked method will never get executed.

When you call XposedHelpers.findAndHookMethod the callback can either be
  • XC_MethodHook : Callback class for method hooks. Usually, anonymous subclasses of this class are created which override beforeHookedMethod(XC_MethodHook.MethodHookParam) and/or afterHookedMethod(XC_MethodHook.MethodHookParam).
  • XC_MethodReplacement : A special case of XC_MethodHook which completely replaces the original method.

1st one just provides you the hooks to execute methods before and after original method where as 2nd one replaces it completely.

  • Next create a file called xposed_init in your assets folder and add your fully qualified class name to it. This file tells xposed installer where to look for the module class. For eg in our case it will be -
com.osfg.flashlightxpmodule.XPFlashLightKiller


  • Finally download XposedbridgeApi.jar from here and add it in your app folder. Make sure the version of this jar should be same as xposedminversion set in the meta data tags in step 1.
Now build your app and deploy on your device. Once done Xposed installed should detect it. Just enable the module and reboot the device -



Testing out the module


Just to give you some background on the original Flashlight app. It's code is something like below -

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
    {
        switch (requestCode)
        {
            case CAMERA_PERMISSION:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Received Camera permission");
                    if(!isFlashSupported()) {
                        Toast.makeText(getApplicationContext(), "LED Flash is not supported on your device",Toast.LENGTH_LONG).show();
                        finish();
                    }

                }
                else {
                    Log.d(TAG, "Camera permission denied");
                    Toast.makeText(this, "Please provide camera permission", Toast.LENGTH_SHORT).show();
                    finish();
                }
                return;
        }
    }



It first asks for permission. If you give it then it will check if flash is supported on the device. So if you have already given Flashlight app camera permission then remove it from settings and open the app again. Now when it prompts for permissions again give it. But now you can see that the app shuts down with toast message - "Flash not supported on this device". This is because we hooked into isFlashSupported() method and made it always return false. So that the app never works :)

NOTE : From next time it will work fine. Since permission is already given next time it will not prompt and never execute  isFlashSupported() method. To retest remove the permissions from settings again.





You can also see following line in logcat -

03-20 02:36:37.864 8002-8002/? I/Xposed: Loaded app: com.osfg.flashlight

03-20 02:36:44.123 8002-8002/? I/Xposed: Hooken method isFlashSupported of class com.osfg.flashlight.FlashLightActivity


Complete code is added in my github repo -

Related Links


t> UA-39527780-1 back to top