Monday, July 25, 2016

Sending a Toast from a Service

Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() { @Override public void run()
{
    Toast.makeText(getApplicationContext(), "Detection Service Failed, restart it", Toast.LENGTH_LONG).show();
}
});

Sunday, June 19, 2016

Security Camera Help


Security Camera is an Advanced Security / Spy / Baby, Pet Monitor App, available for free download on Google Play.

Operation Modes

Motion detection with video

This is the default operation mode, it will take image every x seconds (default is 3), 
and will run motion detection on it. If motion is detected, 
it will upload a the picture to your Google Drive
Account, and will record a video.
The quality of the video can be controlled from the app setting,
it will effect the time of the recorded video.
The lower the video quality the longer the video.

Pictures Only

The same as above, but will not record a video (will only upload a picture).

Video Burst

Records a sequence of videos (The default is 4).
After the recording is over, will return to the previous mode of operation.
This mode is useful when a major event is detected, and you need to have more information on it.
This (as all over modes) can be controlled remotely by the controller.

Audio Only

The same as the first mode, but instead of recording video, it will record a high quality audio.
This mode is useful when audio is more important, and you want to save battery, and get the uploads 
media more quickly.


The Controller

All your detectors (phone devices that acts as motion detectors), can be controlled remotely
Using the Controller,
Your detectors can be anywhere in the world, in a place with internet connection,
And your controller will be able to control them.
The controller will send the control command to the detectors (you will see the message: 
"command was sent"), and the detector will act according to the new command.
For example, if you changed the mode to video burst, all the videos from the video burst, 
will be pushed to your Viewer/CTRL device. 
If you change the video to high quality, a high quality video will be pushed to your device.




Tips & Tricks

+ Its recommended to connect the detector to a power source. 
It could be a regular charger, but if not available, you can use a power stick.
Our tests shows that on regular use, a detector could last without power for around 6 hours.
But since most devices used as a detectors are old ones (and their battery is used), 
the above is recommended. 

+ Make sure your device WiFi is not configured to stop when the device sleeps.



+ When detection service is on, its recommended to leave the security camera app on the front 
Before switching the device screen off (putting device to sleep).
This way Android will not kill the detection service.

+ When done with the detection service, 
make sure you switch it off before continuing using your 
device as a regular device.
Failing to do so will drain the battery an upload unnecessary media to your Google Drive. 




+ You can setup your device as as both Detector and Viewer for testing purposes.
  To do this, simply restart the wizard (options, Restart Setup Wiz), and setup your device 
  Again, make sure you use the same Google Account.

  

  This way your device will be able to capture images and videos, and also get them via push
  Notifications
  


Tuesday, May 31, 2016

Google play Error 505

This is a nasty one,
It happens when trying to install an app for some unknown reason,
I heard of some users that did factory reset to solve this.

Basically all you have to do is to use adb to uninstall the app, and you will be able to install again the app from Google Play.

Install Google Android SDK.
Open Command Console
run:
adb uninstall <the app full package name>

Wednesday, April 20, 2016

Re-Using AsyncTask - Calling from a regular Thread (that is not the UI Thread)

The problem:
You can't call a Class the extends AsyncTask from a thread that is not the UI Thread.

The Solution:

This is a design pattern that will enable your AsyncTask Class methods to be called from a thread.

1. define your operation methods as public static
2. your doInBackground should call one of the operation methods (according to its params).

Here is an example:

public class GCMSender extends AsyncTask<String, Integer, Integer> {
    @Override    protected Integer doInBackground(String... params) {
        Log.i(P.TAG, "GcmSender doInBackground called");

        if (params.length == 2) {
            //// sending GCM            String topic = params[0];
            String pic_name = params[1];
            sendNewDetected(topic, pic_name);
        }
        else  {
            Log.e(P.TAG, "GcmSender cant send GCM params length is not 2");
            return 0;
        }
        return 0;

    }


    // this is the operation method
    public static void sendNewDetected(String topic, String pic_id) {
        Log.i(P.Tag, "GcmSender send called");
        try {
            // Prepare JSON containing the GCM message content. What to send and where to send.            JSONObject jGcmData = new JSONObject();
            JSONObject jData = new JSONObject();

            String fullTopic = "/topics/" + topic;
            Log.i(P.Tag, "jData name: " + pic_id);
            Log.i(P.Tag, "fullTopic: " + fullTopic);

            jData.put("pic_id", pic_id);
            jGcmData.put("to", fullTopic);

            // What to send in GCM message.            jGcmData.put("data", jData);

            // Create connection to send GCM Message request.            URL url = new URL("https://android.googleapis.com/gcm/send");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestProperty("Authorization", "key=" + API_KEY);
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);

            // Send GCM message content.            OutputStream outputStream = conn.getOutputStream();
            outputStream.write(jGcmData.toString().getBytes());

            // Read GCM response.            InputStream inputStream = conn.getInputStream();
            //String resp = IOUtils.toString(inputStream);            String resp = convertStreamToString(inputStream);

            Log.i(P.Tag, "GCM message sent: " + resp);

        } catch (IOException e) {
            Log.e(P.Tag, "Unable to send GCM message.");
        }
    }

    }


To call from a thread use this:

GCMSender.sendNewDetected(P.getAccount4GCM(getApplicationContext()), fileId);


To call as AsyncTask use this:

new GCMSender().execute(P.getAccount4GCM(getApplicationContext()), fileId);


Tuesday, March 29, 2016

Google Official Drive API for Android

Limitations

Works locally

  • If one device add file to  a folder, the other device will not see the new file (on appFolder it worked).
  • If on the device I use Drive app to update a file, and app on the same device that use the drive api will not get a notification
  • Events (both ChangeSubsciption EventService, and changeListener)  work only if the app updates a file via drive api on the same device.
  • Events will not work at all on folders (checked regular folder and appFolder) checked cases of Updating folder "as starred" (which worked with files). Also uploading files didn't had effect on the folder event (event was not thrown).

Other Limitations

  • The appFolder is not visible in anyway and should be handled from the app only.
  • Looks like Google is aware of this limitations, and they was inserted on purpose (for security reasons, and it will not be changed anytime soon (if at all).


Things that work

  • Upload file (media file, or any other file). there is no upload in the api, there is an awkward way to achieve it:
final DriveContents driveContents = result.getDriveContents();

    // Perform I/O off the UI thread.    new Thread() {
        @Override        public void run() {
            // write content to DriveContents            OutputStream outputStream = driveContents.getOutputStream();

            try {
                File picFile;
                picFile = new File("/sdcard/DETECTED_GALLERY/1458374357190.jpg");
                byte[] byteStream = new byte[(int) picFile.length()];
                FileInputStream fileInputStream = new FileInputStream(picFile);
                fileInputStream.read(byteStream);
                fileInputStream.close();

                outputStream.write(byteStream);
                outputStream.close();
            } catch (IOException e) {
                Log.e(P.TAG, e.getMessage());
            }

            MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                    .setTitle("test_pic12")
                    .setMimeType("image/jpeg")
                    .setStarred(true).build();

            Drive.DriveApi.getAppFolder(getGoogleApiClient()).createFile(getGoogleApiClient(), changeSet, driveContents)
                    .setResultCallback(fileCallback);

           
        }
    }.start();
}

  • Create folder works, not sure if one device created the folder (via the api) the other device will see it (need to check).
  • The Event service works without starting it (when it works, see the limitations above) <service android:name=".MyDriveEventService" android:exported="true">
            <intent-filter>
                <action android:name="com.google.android.gms.drive.events.HANDLE_EVENT"/>
            </intent-filter>
        </service>
  • Using the appFolder I can list files that was added from another device.

Monday, March 14, 2016

Accesing UI From Thread of AcyncTask

Handler can is defined in the Activity top level


public class MyActivity extends Activity{

...

private Handler handler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
    Log.i(P.TAG, "Handler got message");
    if (msg.what == GOT_RECIEVER_HOST) {
    Log.i(P.Tag, "GOT_RECIEVER_HOST");
              Toast.makeText(ctxStatic, "Motion Detected", Toast.LENGTH_LONG).show();
      }
    }
    };

...
}


From a Thread, or AsyncTask in the Activity


handler.sendEmptyMessage(GOT_RECIEVER_HOST);

Wednesday, March 9, 2016

Custom Dialog that access its own UI elements

public boolean onContextItemSelected(MenuItem item) {
    switch (item.getItemId()) {
case RENAME_ID:

final MenuItem itemF = item;
AlertDialog.Builder renameDialogBuilder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_group_rename, null);
renameDialogBuilder.setView(dialogView);
final View dialogViewF = dialogView;

renameDialogBuilder.setTitle(getString(R.string.rename_group)).

        setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                Log.d(P.Tag, "OK clicked");

                EditText groupName = (EditText)dialogViewF.findViewById(R.id.group_rename);
                String groupNameStr = groupName.getText().toString();
                if (groupNameStr.length() < 5) {
                    Toast.makeText(getActivity().getApplicationContext(), getString(R.string.groupname_invalid), Toast.LENGTH_LONG).show();
                    return;
                }


                AdapterView.AdapterContextMenuInfo info= (AdapterView.AdapterContextMenuInfo)itemF.getMenuInfo();
                Log.d(P.TAG, "Renaming ID: " + info.id);
                int position = (new Long(info.id)).intValue();
                try {
                    String groupId = ((JSONObject)jsonGroupArray.get(position)).get("id").toString();
                    String adminId = ((JSONObject)jsonGroupArray.get(position)).get("admin_id").toString();
                    String userId = P.getUserId(getActivity().getApplicationContext());
                    if (userId.equals(adminId)) {
                        Log.d(P.TAG, "Renaming groupId: " + groupId);
                        new HttpCallAsyncTask(getActivity()).execute("oper", "rename_group", "user_id", userId, "group_id", groupId, "group_name", groupNameStr);
                    }


                } catch (JSONException e) {
                    Log.e(P.TAG, "JSONException when trying to leave position: " + position );
                    Toast.makeText(getActivity().getApplicationContext(), getString(R.string.delete_failed), Toast.LENGTH_LONG).show();
                }


            }
        }).
        setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                Log.i(P.TAG, "cancel clicked, doing nothing");
            }
        });
AlertDialog alert  = renameDialogBuilder.create();
alert.show();