Sunday, April 15, 2018

Android - Native Development Kit (NDK)

Introduction: As we know, Java is the default programming language to make an Android application. But these languages are not the best solution for some time. Like if you want to make games. Where we need fast calculation. Today, a lot of gaming engines are working with c/c++. To achieve it in Android Google offers the Android Native Development Kit (NDK).

What is the NDK?

The NDK allows developers to write optimized code in C/C++ that compiles to native code. With the NDK, you can make games with better performance on Android devices. We have the following reason to use NDK to make game.

1. Memory management and performance.
2. You can use Renderscript
3. C/C++ have a large body of existing legacy code.
4. We can develop a multiplatform application, the NDK is unbeatable in this domain. Since the same code written in C + + for Android can be easily ported and run the same way on the iOS, Windows or any other platform without changing the original code.

But in some cases, NDK is not recommended, Because.

1. Harder to debug
2. Reduce flexibility.

How it works?

As we know everything in an Android application is execute in the context of the Dalvik Virtual Machine or ART. In order to integrate your C/C++ code with this environment. We have to use the JNI (Java Native Interface). This is how the Java and C/C++ components talk to each other.

For use it in Android, We have to call System.loadLibrary("Name of file") static method from Java. When you load a .so file, its JNI_OnLoad() function is called if it exists and functions starting with Java_ are mapped to the corresponding Java methods. These methods are to be declared in Java classes and have the "keyword" native to specify that their implementation comes from a .so file.
public native int numbers(int x, int y);

At runtime, when the Java execution reaches a method with its implementation being native. This native implementation is directly executed. When it returns, the Java code execution continues.

The JNI also gives you jni.h header, that gives you the methods to access the Java environment (JavaVM*, JNIEnv*) so you can manipulate, create and access Java primitives (jint, jlong.), objects (jobject, jclass.), exceptions (jthrowable), etc.

NDK setup with Android studio

Android Studio only includes the default tools and SDK. So, you can download and install the NDK package separately.






Example


So, Let create your first JNI application written in C/C++ with Android Studio.




Once you have done all the steps. You will see cpp folder that contains all C/C++ files.




The “ndk-build” script is responsible for automatically synch with the project and determining what to build. The script is also responsible for generating binaries and copying those binaries to your app’s project path.

There are also native shared libraries (.so) that the NDK builds from the native source code and Native static libraries (.a), and these can be linked against other libraries. The Application Binary Interface (ABI) uses the .so files to know exactly how your app’s machine code will work with the system when the app is running. The NDK supports ARMABI by default along with MIPS and x86.

Here, We have java file that load and map with C/C+ methods.
public class MainActivity extends AppCompatActivity {
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(native_say_hello_world());
    }
    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    private native String native_say_hello_world();
}

On the side c++ side, We have created a cpp file and a method that refers to the native method with that was created during step #2. The method has to be named correctly, including the name of the Java package and class. This method can have parameters and returns Java classes such as jobject, jstring, jint or whatever that starts with the letter j. We can return void as well.
JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_native_say_hello_world(JNIEnv* env, jobject thiz ) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}




Share:

Wednesday, April 11, 2018

Android - Google map cluster utility

Introduction:  Google map is a great way to show the user current location in the world and some time we need to show more than one image at a location. The cluster is nothing more than a group of markers combined together.

To achieve it in Android google provide us dependencies that we have to add in the application grade file.
compile 'com.google.android.gms:play-services-maps:9.0.2'
compile 'com.google.maps.android:android-maps-utils:0.4.3'

Here, i'm going to explain a clustering utility that i have used in one of my projects.  So, for use it you have to provide an instance of google map and screen context in the constructor of utility and call init() method for initial setup. And call addUser() method with locations of the item.
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.crashlytics.android.Crashlytics;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterItem;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.clustering.view.DefaultClusterRenderer;
import com.google.maps.android.ui.IconGenerator;
import java.util.List;
public class GoogleMapClusterUtil implements ClusterManager.OnClusterClickListener <GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterInfoWindowClickListener<GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterItemClickListener<GoogleMapClusterUtil.Person>,   ClusterManager.OnClusterItemInfoWindowClickListener<GoogleMapClusterUtil.Person> {
    private final Context mContext;
    private final GoogleMap mGoogleMap;
    private ClusterManager<Person> mClusterManager;
    public GoogleMapClusterUtil(Context context, GoogleMap googleMap) {
        mContext = context;
        mGoogleMap = googleMap;
    }
    /**
     * Create instance of cluster.
     */
    public void init() {
        mClusterManager = new ClusterManager<>(mContext, mGoogleMap);
        mClusterManager.setRenderer(new PersonRenderer());
        mGoogleMap.setOnMarkerClickListener(mClusterManager);
        mGoogleMap.setOnInfoWindowClickListener(mClusterManager);
        mClusterManager.setOnClusterClickListener(this);
        mClusterManager.setOnClusterInfoWindowClickListener(this);
        mClusterManager.setOnClusterItemClickListener(this);
        mClusterManager.setOnClusterItemInfoWindowClickListener(this);
        mClusterManager.cluster();
}
 
    /**
     * Update cluster on camera idle.
     */
    public void onCameraIdle() {
        mClusterManager.onCameraIdle();
    }
    public void addUser(List<Location> userNearMe) {
        clearAllItem();
        for (Location appUserLocation : userNearMe) {
            // the address is blank for now, will be used later
            mClusterManager.addItem(new Person(new LatLng(appUserLocation.getLatitude(), appUserLocation.getLongitude()), 0));
        }
    }
  
    /**
     * Delete all cluster from map.
     */
    public void clearAllItem() {
        mClusterManager.clearItems();
    }
    @Override
    public boolean onClusterClick(Cluster<Person> cluster) {
      
        // Zoom in the cluster. Need to create LatLngBounds and including all the cluster items
        // inside of bounds, then animate to center of the bounds.
        // Create the builder to collect all essential cluster items for the bounds.
        LatLngBounds.Builder builder = LatLngBounds.builder();
        for (ClusterItem item : cluster.getItems()) {
            builder.include(item.getPosition());
        }
        // Get the LatLngBounds
        final LatLngBounds bounds = builder.build();
        // Animate camera to the bounds
        try {
            mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
        } catch (Exception e) {
            Crashlytics.logException(e);
        }
        return true;
    }
    @Override
    public void onClusterInfoWindowClick(Cluster<Person> cluster) {
        // onClusterInfoWindowClick
    }
    @Override
    public boolean onClusterItemClick(Person person) {
     
        return false;
    }
    @Override
    public void onClusterItemInfoWindowClick(Person person) {
        // onClusterItemInfoWindowClick
    }
   
    public class Person implements ClusterItem {
        public final int id;
        private final LatLng mPosition;
        private Person(LatLng position, int id) {
            this.id = id;
            mPosition = position;
        }
        public int getId() {
            return id;
        }
        @Override
        public LatLng getPosition() {
            return mPosition;
        }
        @Override
        public String getTitle() {
            return null;
        }
        @Override
        public String getSnippet() {
            return null;
        }
    }
    /**
     * Manage cluster update.
     */
    private class PersonRenderer extends DefaultClusterRenderer<Person> {
        private PersonRenderer() {
            super(mContext, mGoogleMap, mClusterManager);
        }
        @Override
        protected void onBeforeClusterItemRendered(Person person, MarkerOptions markerOptions) {
            // Draw a single person.
            // Set the info window to show their name.
            if (person.getId()>0){
                markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.location_package));
            }else{
                markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.location));
            }
        }
        @Override
        protected void onBeforeClusterRendered(Cluster<Person> cluster, MarkerOptions markerOptions) {
            // Draw multiple people.
            // Note: this method runs on the UI thread. Don't spend too much time in here (like in this example).
            IconGenerator iconGenerator = new IconGenerator(mContext);
            iconGenerator.setBackground(null);
            LayoutInflater myInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View activityView = myInflater.inflate(R.layout.item_cluster, null, false);
            activityView.findViewById(R.id.item_cluster_iv_cluster_icon).setBackgroundResource(R.drawable.location_blank);
            if (cluster.getSize() > 99) {
                ((TextView) activityView.findViewById(R.id.amu_text)).setText(String.valueOf("99+"));
            } else {
                ((TextView) activityView.findViewById(R.id.amu_text)).setText(String.valueOf(cluster.getSize()));
            }
            iconGenerator.setContentView(activityView);
            BitmapDescriptor icon = BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon());
            markerOptions.icon(icon);
        }
        @Override
        protected boolean shouldRenderAsCluster(Cluster cluster) {
            // Always render clusters.
            return cluster.getSize() > 1;
        }
    }
}


for display cluster google map i have used following XML file. You can change it according to your requirement.
layout: item_cluster
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:app="http://schemas.android.com/apk/res-auto"
             android:layout_width="30dp"
             android:layout_height="30dp"
             android:padding="2dp">
    <ImageView
        android:id="@+id/item_cluster_iv_cluster_icon"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@null"/>
    <TextView
        android:id="@+id/amu_text"
        android:layout_width="wrap_parent"
        android:layout_height="wrap_parent"
        android:layout_gravity="center"
        android:gravity="center"
        android:paddingBottom="2dp"
        />
</FrameLayout>



At the end build it in your project. You will see, after double click on the map. it will expand item.


 




Share:

Sunday, March 4, 2018

Firebase - Introduction of Cloud Functions

Back-end of any application is a heart that performs all task for the end user. Back-end executes tasks like: send push notifications, email and update other user information. It means that you no longer have to deploy your own server. You need only deploy your functions and all done. That's great for a mobile developer.
Share:

Saturday, January 20, 2018

Android Intent

Today, I'm going to discuss some small snippet of Intent. An Intent is basically a message that is passed between components (such as Activities, Services, Broadcast Receivers, and Content Providers). One component that wants to invoke another has to only express its intent to do a job. And any other component that exists and has claimed that it can do such a job through intent-filters, is invoked by the Android platform to accomplish the job. This means, neither components are aware of each other's existence but can still work together to give the desired result for the end-user.


An intent is an abstract description of an operation to be performed. It can be used with startActivity to launch an Activity, broadcastIntent to send it to any interested BroadcastReceiver components, and startService(Intent) or bindService(Intent, ServiceConnection, int) to communicate with a Background Service.


Intent perform late runtime binding between the code in different applications.

Action: The general action to be performed, such as ACTION_VIEW, ACTION_EDIT, ACTION_MAIN, etc.

Data: The data to operate on, such as a personal record in the contacts database, expressed as a Uri.


Now let's see some code snippet.

1. Open camera.

 public Intent openCamera(Uri mCameraOutput) {
        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCameraOutput);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
            cameraIntent.putExtra("android.intent.extras.LENS_FACING_FRONT", 1);
        } else {
            cameraIntent.putExtra("android.intent.extras.CAMERA_FACING", 1);
        }
        return cameraIntent;
    }

2. Open Gallery.

    public Intent getGallery() {
        Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        String[] mimeTypes = {"image/jpeg", "image/jpg", "image/png"};
        galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
        return galleryIntent;
    }





3. Open dialer screen.


@Override
    public void call(String mobile) {
        Intent intent = new Intent(Intent.ACTION_DIAL);
        intent.setData(Uri.parse("tel:" + mobile));
        startActivity(intent);
    }

4. Move to google play store.

@Override
    public void moveToGooglePlayStore() {
        if (fragmentBaseActivity != null) {
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + fragmentBaseActivity.getPackageName())));
        }
    }

5. Send a mail.

/**
     * Send invitation email.
     */
    private void onShareClick() {

            Intent email = new Intent(Intent.ACTION_SEND);
            email.putExtra(Intent.EXTRA_SUBJECT, fragmentBaseActivity.getString(R.string.share_referral_code));
            email.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(new StringBuilder()
                    .append("<html>")
                    .append("<p>" + fragmentBaseActivity.getString(R.string.hi) + "</p>")
                    .append("<p>" + fragmentBaseActivity.getString(R.string.refer_content) + "</p>")
                    .append("<p><h2>" + mReferCode + "</h2></p>")
                    .append("<p>" + fragmentBaseActivity.getString(R.string.app_google_play_store_content) + "</p>")
                    .append("<p>" + fragmentBaseActivity.getString(R.string.app_link)+" "+ "<a href=>" + "http://play.google.com/store/apps/details?id=" + fragmentBaseActivity.getPackageName() + "</a></p><br>")
                    .append("<p>" + fragmentBaseActivity.getString(R.string.regards) + "</p>")
                    .append("<p>" + mPresenter.getLoginUserName() + "</p>")
                    .append("</html>")
                    .toString()));

            email.setType("text/html");
            startActivity(Intent.createChooser(email, "Choose an Email client :"));
     
    }

Share:

Mobile number verification with Nexmo

Introduction: Nowadays, A small and big application using the user mobile number for registration on their application.
For verifying valid mobile number we have to depend on 3rd party application like Nexmo. Nexmo provides mobile number verification service with a voice call or SMS OTP.

I'm going to explain mobile number verification feature with Android SDK. We have to add a dependency to the application Gradle file.


implementation 'com.nexmo:verify:4.0.0'


Create an account on nexmo.com. After that register their app by using the following steps.

1. Go to Nexmo and click on verify tab.



2. Create an application.



Once you create an application. You can find your application credentials and use it in below snippet.

1. Get an instance of NexmoUtil class and use setListener(this) for the callback.

 public static NexmoUtil getInstance(Context context) {
        if (verifyClient == null) {
            mInstance = new NexmoUtil();
            try {
                NexmoClient nexmoClient = new NexmoClient.NexmoClientBuilder()
                        .context(context)
                        .applicationId(context.getString(R.string.nexmo_application_id))
                        .sharedSecretKey(context.getString(R.string.nexmo_shared_secret_key))
                        .build();
                verifyClient = new VerifyClient(nexmoClient);
            } catch (ClientBuilderException e) {
                Crashlytics.logException(e);
            }
        }
        return mInstance;
    }


2. To get OTP, call sendOtp("IN","mobile number") method of following utility with country code(INDIA-IN) and valid mobile number.

 private void sendOtp(String countryCode, String mobileNumber) {
        if (verifyClient != null && countryCode != null && mobileNumber != null) {
            verifyClient.getVerifiedUser(countryCode, mobileNumber);
        }
    }

3. After some time onVerifyInProgress() will notify you about the progress.

 @Override
    public void onVerifyInProgress(VerifyClient verifyClient, UserObject userObject) {
        if (mNexmoListener != null) {
            mNexmoListener.onMobileVerificationInProgress(verifyClient, userObject);
        }
    }

4. At the end, if you got OTP, verify it by using the checkPinCode("----") method.

 public void checkPinCode(String otp) {
        if (verifyClient != null && otp != null) {
            verifyClient.checkPinCode(otp);
        }
    }



5. If Nexmo find valid OTP onUserVerified() will invoke.


 @Override
    public void onUserVerified(VerifyClient verifyClient, UserObject userObject) {
        if (mNexmoListener != null) {
            mNexmoListener.onMobileNumberVerified(verifyClient, userObject);
        }
    }


View complete utility.



import com.nexmo.sdk.NexmoClient;
import com.nexmo.sdk.core.client.ClientBuilderException;
import com.nexmo.sdk.verify.client.VerifyClient;
import com.nexmo.sdk.verify.event.Command;
import com.nexmo.sdk.verify.event.CommandListener;
import com.nexmo.sdk.verify.event.SearchListener;
import com.nexmo.sdk.verify.event.UserObject;
import com.nexmo.sdk.verify.event.UserStatus;
import com.nexmo.sdk.verify.event.VerifyClientListener;
import com.nexmo.sdk.verify.event.VerifyError;

import java.io.IOException;

/**
 * Handle mobile number verification feature of app.
 */
public class NexmoUtil implements VerifyClientListener, SearchListener {

    private static VerifyClient verifyClient;
    private static NexmoUtil mInstance;
    private NexmoListener mNexmoListener;
    private String mCountryCode;
    private String mMobileNumber;

    public static NexmoUtil getInstance(Context context) {
        if (verifyClient == null) {
            mInstance = new NexmoUtil();
            try {
                NexmoClient nexmoClient = new NexmoClient.NexmoClientBuilder()
                        .context(context)
                        .applicationId(context.getString(R.string.nexmo_application_id))
                        .sharedSecretKey(context.getString(R.string.nexmo_shared_secret_key))
                        .build();
                verifyClient = new VerifyClient(nexmoClient);
            } catch (ClientBuilderException e) {
                Crashlytics.logException(e);
            }
        }
        return mInstance;
    }

    /**
     * Set Callback listener.
     *
     * @param listener instance.
     */
    public void setListener(NexmoListener listener) {
        mNexmoListener = listener;
        if (verifyClient != null) {
            verifyClient.removeVerifyListeners();
            verifyClient.addVerifyListener(this);
        }
    }

    @Override
    public void onException(IOException e) {
        Crashlytics.logException(e);
        if (mNexmoListener != null) {
            mNexmoListener.onMobileVerificationError(null);
        }
    }

    @Override
    public void onVerifyInProgress(VerifyClient verifyClient, UserObject userObject) {
        if (mNexmoListener != null) {
            mNexmoListener.onMobileVerificationInProgress(verifyClient, userObject);
        }
    }

    @Override
    public void onUserVerified(VerifyClient verifyClient, UserObject userObject) {
        if (mNexmoListener != null) {
            mNexmoListener.onMobileNumberVerified(verifyClient, userObject);
        }
    }

    @Override
    public void onError(VerifyClient verifyClient, VerifyError errorCode, UserObject user) {
        if (mNexmoListener != null) {
            mNexmoListener.onMobileVerificationError(errorCode);
        }
    }

    /**
     * Send number for get OTP.
     *
     * @param countryCode  country code.
     * @param mobileNumber mobile number.
     */
    public void getVerifiedUser(final String countryCode, final String mobileNumber) {
        mCountryCode = countryCode;
//        mCountryCode = "IN"
        mMobileNumber = mobileNumber;
        verifyClient.getUserStatus(mCountryCode, mobileNumber, NexmoUtil.this);
    }

    /**
     * Cancel verified number.
     *
     * @param countryCode  country code.
     * @param mobileNumber mobile number.
     */
    private void cancelVerification(final String countryCode, final String mobileNumber) {
        verifyClient.command(countryCode, mobileNumber, Command.LOGOUT, new CommandListener() {
            @Override
            public void onSuccess(Command command) {
                sendOtp(countryCode, mobileNumber);
            }

            @Override
            public void onError(Command command, VerifyError verifyError, String s) {
                if (mNexmoListener != null) {
                    mNexmoListener.onMobileVerificationError(verifyError);
                }
            }

            @Override
            public void onException(IOException e) {
                Crashlytics.logException(e);
                if (mNexmoListener != null) {
                    mNexmoListener.onMobileVerificationError(null);
                }
            }
        });
    }

    /**
     * Get OPT
     *
     * @param countryCode  country code.
     * @param mobileNumber mobile number.
     */
    private void sendOtp(String countryCode, String mobileNumber) {
        if (verifyClient != null && countryCode != null && mobileNumber != null) {
            verifyClient.getVerifiedUser(countryCode, mobileNumber);
//          verifyClient.getVerifiedUser("IN", mobileNumber)
        }
    }

    /**
     * Verify OTP.
     *
     * @param otp value.
     */
    public void checkPinCode(String otp) {
        if (verifyClient != null && otp != null) {
            verifyClient.checkPinCode(otp);
        }
    }

    @Override
    public void onUserStatus(UserStatus userStatus) {
        if (userStatus == UserStatus.USER_VERIFIED) {
            cancelVerification(mCountryCode, mMobileNumber);
        } else {
            sendOtp(mCountryCode, mMobileNumber);
        }
    }

    @Override
    public void onError(VerifyError verifyError, String s) {
        if (mNexmoListener != null) {
            mNexmoListener.onMobileVerificationError(verifyError);
        }
    }

    public interface NexmoListener {

        /**
         * Number verification under progress.
         *
         * @param verifyClient instance.
         * @param userObject   instance.
         */
        void onMobileVerificationInProgress(VerifyClient verifyClient, UserObject userObject);

        /**
         * Enter OTP verified.
         *
         * @param verifyClient instance.
         * @param userObject   instance.
         */
        void onMobileNumberVerified(VerifyClient verifyClient, UserObject userObject);

        /**
         * Nexmo API's error.
         *
         * @param verifyError instance.
         */
        void onMobileVerificationError(VerifyError verifyError);
    }
}
Share:

Saturday, August 12, 2017

Kotlin - Introduction

Kotlin is a statically-typed programming language ( A programming language is said to use static typing when type checking is performed during compile-time as opposed to run-time) that runs on the Java Virtual Machine and also can be compiled to JavaScript source code or uses the LLVM compiler infrastructure. Its primary development is from a team of JetBrains programmers based in Saint Petersburg, Russia.




Before start learning Kotlin, Let's take a look at Kotlin feature that makes it smart.

Share:

Sunday, April 30, 2017

Android - Moving an object on a circular path


Overview: -  Android provides powerful API libraries which support custom 2D and 3D graphics. We can move an object on the circular path by using canvas and doing some mathematics calculation.



You can use the parametric equation of a circle. Considering the circle is drawn with the center on the origin (O) as shown in the diagram below



If we take a point "p" on the circumference of the circle, having a radius r.

Let the angle made by OP (Origin to p) be θ. Let the distance of p from x-axis be y Let the distance of p from y-axis be x

Using the above assumptions we get the triangle as shown below:

Now we know that cos θ = base/hypotenuse and sin θ = perpendicular/hypotenuse

which gives us cos θ = x/r and sin θ = y/r

If the circle is not at the origin and rather at (a,b) then we can say that the center of the circle is shifted

a unit in the x-axis
b unit in the y-axis 

So for such a circle, we can change the parametric equation accordingly by adding the shift on the x and y-axis giving us the following equations:

x=a+r*cosθ
y=b+r*sinθ 

Where a & b are the x,y coordinates of the center of the circle.

Hence we found x and y the coordinates of the point on the circumference of the circle with radius r

By using the above calculation, We can move an object by using the above method. As we can see:






Now, Let's implement it in Android.

To implement it, we going to use SurfaceView.

import android.content.Context;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class CircularView extends SurfaceView implements SurfaceHolder.Callback2 {

    private SurfaceHolder mHolder;

    private int mViewWidth;
    private int mViewHeight;
    private Circle mBigCircle;

    private AnimationThread galleryThread = null;

    public CircularView(Context context) {
        super(context);

        mHolder = getHolder();

        mHolder.addCallback(this);
    }

    @Override
    public void surfaceRedrawNeeded(SurfaceHolder holder) {
        drawViewOnSystemCall(holder);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mViewWidth = getWidth();
        mViewHeight = getHeight();

        mBigCircle = new Circle(mViewWidth / 2, mViewHeight / 2, (Math.round((mViewWidth * 80)          / 100)) / 2);

        drawViewOnSystemCall(holder);

        galleryThread = new AnimationThread(getHolder());
        galleryThread.setRunning(true);
        galleryThread.start();
    }

    private void drawViewOnSystemCall(SurfaceHolder holder) {
        synchronized (holder) {
            refreshScreen();
        }
    }

    private void refreshScreen() {
        Canvas lockCanvas = mHolder.lockCanvas();
        drawSurfaceView(lockCanvas);
        mHolder.unlockCanvasAndPost(lockCanvas);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        drawViewOnSystemCall(holder);
    }

    private void drawSurfaceView(Canvas canvas) {
        if (canvas == null) {
            return;
        }
        canvas.drawRGB(255, 255, 255);
        if (mBigCircle != null) {
            mBigCircle.render(canvas);
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

    class AnimationThread extends Thread {
        private boolean isRunning;
        private SurfaceHolder mHolder;

        public AnimationThread(SurfaceHolder holder) {
            mHolder = holder;
        }

        public void setRunning(boolean running) {
            isRunning = running;
        }

        @Override
        public void run() {
            while (isRunning) {
                mBigCircle.move();
                drawViewOnSystemCall(mHolder);
            }
        }
    }
}

Create a circle object that shows a circle border and moving object.

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

public class Circle {

    private float mUserPicCenterX;
    private float mUserPicCenterY;

    private float mUserPicBorderCenterX;
    private float mUserPicBorderCenterY;
    private int mBorderRadius;
    private Paint mVisiblePaint;
    private Paint mVisibleMessageCountPaint;
    private double angle = 230;
    private int mVisibleMessageCountRadius;

    public Circle(float centerX, float centerY, int radius) {
        mUserPicCenterX = centerX;
        mUserPicCenterY = centerY;
        mBorderRadius = radius;
        createVisiblePaint();
        updatePosition(angle);
    }


    private void updatePosition(double angle) {
        angle = Math.toRadians(angle);
        mUserPicBorderCenterX = (float) (mUserPicCenterX + Math.cos(angle) * mBorderRadius);
        mUserPicBorderCenterY = (float) (mUserPicCenterY + Math.sin(angle) * mBorderRadius);
    }

    private void createVisiblePaint() {
        mVisiblePaint = new Paint();
        mVisiblePaint.setAntiAlias(true);
        mVisiblePaint.setFilterBitmap(true);
        mVisiblePaint.setDither(true);
        mVisiblePaint.setColor(Color.parseColor("#F85A74"));
        mVisiblePaint.setStyle(Paint.Style.STROKE);
        mVisiblePaint.setStrokeWidth(14f);

        mVisibleMessageCountPaint = new Paint();
        mVisibleMessageCountPaint.setAntiAlias(true);
        mVisibleMessageCountPaint.setFilterBitmap(true);
        mVisibleMessageCountPaint.setDither(true);
        mVisibleMessageCountPaint.setColor(Color.parseColor("#F85A74"));

        mVisibleMessageCountRadius = mBorderRadius / 6;

    }

    public void render(Canvas canvas) {
        canvas.drawCircle(mUserPicCenterX, mUserPicCenterY, mBorderRadius, mVisiblePaint);
        canvas.drawCircle(mUserPicBorderCenterX, mUserPicBorderCenterY, mVisibleMessageCountRadius, mVisibleMessageCountPaint);
    }


    public void move() {
        if (angle > 360) {
            angle = 0;
        }
        angle++;
        updatePosition(angle);
    }
}

Share:

Saturday, March 18, 2017

Android - App Shortcuts, Android Nougat 7.1 Feature.

Android Nougat 7.1, the newest version of Android has come with several new features. Here I'm going to explain App Shortcuts. App Shortcuts allows the user to move on a specific screen from the app launcher. The feature is available on any launcher that supports them, such as YouTube app launchers, Android Nougat 7.1.


App Shortcuts, YouTube in Android Nougat 7.1


To reveal the shortcuts of an app, simply long-press the launcher icon of that app. Then tap on a shortcut to jump to the associated action. These shortcuts are a great way to engage users and provide some menu options of your app even before users launch your app.

Each shortcut references an intent, each of which launches a specific action or task, and you can create a shortcut for any action that you can express as an intent. For example, you can create intents for sending a new text message, making a reservation, playing a video, continuing a game, loading a map location, and much more.

Implement it we have two ways

 A) Statically  (by declaring all the shortcuts in a resource file, also known as manifest shortcuts).

 B) Dynamically (by adding the shortcuts at runtime).

Let's talk about the static approach.

1. Create a shortcuts.xml file and keep it on following location in the project.

 res/xml/shortcuts.xml
 res/xml-v25/shortcuts.xml
 <shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
            <shortcut
               android:enabled="true"
               android:icon="@drawable/ic_launcher"
               android:shortcutDisabledMessage="@string/shortcut_label_disabled"
               android:shortcutId="new_task"
               android:shortcutLongLabel="@string/shortcut_label_create_new_task"
               android:shortcutShortLabel="@string/shortcut_label_new_task">
                  <intent
                     android:action="android.intent.action.VIEW"
                     android:targetClass="com.test.launchershortcut.DetailActivity"
                     android:targetPackage="com.test.launchershortcut"/>
            </shortcut>

            <shortcut
               android:enabled="true"
               android:icon="@drawable/ic_launcher
               android:shortcutDisabledMessage="@string/shortcut_label_disabled"
               android:shortcutId="opened_tasks"
               android:shortcutLongLabel="@string/shortcut_label_view_opened_tasks"
               android:shortcutShortLabel="@string/shortcut_label_opened_tasks">
                  <intent
                    android:action="android.intent.action.VIEW"
                    android:targetClass="com.test.launchershortcut.ReportActivity
                    android:targetPackage="com.test.launchershortcut"/>
             </shortcut>

  </shortcuts>  

As we can see each shortcut tag defines a series of attributes, like the icon and labels, and also references an intent which is set to launch a specific activity when triggered. Attributes android:shortcutId is a mandatory attribute. If it is not declared it will cause the respective shortcut not to appear in the shortcuts list. The recommended maximum number of shortcuts is 4, although it is possible to publish up to 5.


2. Reference shortcuts.xml file in the AndroidManifest.xml as metadata to the App’s launcher activity.
<activity android:name=".MainActivity">
     <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
     <meta-data
           android:name="android.app.shortcuts"
           android:resource="@xml/shortcuts" />
</activity>

Any activity that has the intent-filter action set to android.intent.action.MAIN and the category to android.intent.category.LAUNCHER can display app shortcuts.

Now run the project, the result might look something like this



Implementation of Dynamically Approach. 

For handle Static and Dynamic App shortcuts Nougat 7.1 provides us ShortcutManager. ShortcutManager is the entry point for manipulating (adding, updating, removing) shortcuts at runtime. 
private void createDynamicAppShortCut() {
       ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
        Intent intent = new Intent(this, DetailActivity.class);
        intent.setAction(Intent.ACTION_VIEW);
        ShortcutInfo shortcut = new ShortcutInfo.Builder(this,                    getString(R.string.shortcut_label_new_task_dy))
                .setShortLabel(getString(R.string.shortcut_label_new_task_dy))
                .setLongLabel(getString(R.string.shortcut_label_new_task_dy))
                .setIcon(Icon.createWithResource(this, R.drawable.ic_launcher))
                .setIntent(intent)
                .build();
        shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
}

Trigger the above method at a runtime and move to App Launcher, You will see, we have one more shortcut.



ShortcutManager provides us with some interesting methods.

updateShortcuts (List ids) – update all existing shortcuts by ID.

removeDynamicShortcuts (List ids) – delete dynamic shortcuts by ID.

disableShortcuts (List ids) – disable dynamic shortcuts by ID.

reportShortcutUsed (String shortcutId) – You want to call this method whenever the user selects the shortcut containing the given ID or when the user completes an action in the application that is equivalent to selecting the shortcut. 


Something more

If you tap and drag a shortcut then it will be pinned to the device’s launcher:



When App Shortcuts disable or updated as used, Icon color will be changed. Something like this





Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...