Skip to content

Instantly share code, notes, and snippets.

@emil10001
Last active August 29, 2015 13:57
Show Gist options
  • Save emil10001/9456564 to your computer and use it in GitHub Desktop.
Save emil10001/9456564 to your computer and use it in GitHub Desktop.
These are notes for a presentation on building a GifCamera Glassware - http://goo.gl/vZrxSQ
<activity
android:name=".GGMainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light.NoActionBar.Fullscreen">
<!-- ... -->
</application>
<activity
android:name=".GGMainActivity"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:immersive="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="com.google.android.glass.VoiceTrigger"
android:resource="@xml/voice_launch" />
</activity>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.feigdev.gg_test" >
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light.NoActionBar.Fullscreen">
<activity
android:name=".GGMainActivity"
android:label="@string/app_name"
android:immersive="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="com.google.android.glass.VoiceTrigger"
android:resource="@xml/voice_launch" />
</activity>
</application>
</manifest>
dependencies {
// requires library project https://github.com/emil10001/ReusableAndroidUtils
compile project(':ReusableAndroidUtils')
}
android {
compileSdkVersion "Google Inc.:Glass Development Kit Sneak Peek:15"
buildToolsVersion "19.0.1"
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<ImageView
android:id="@+id/cur_img"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:layout_gravity="center"/>
</FrameLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.feigdev.gg_runthrough.MainActivity$ImageGrabFrag">
<SurfaceView
android:id="@+id/preview1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true" />
</RelativeLayout>
public class GGMainActivity extends Activity implements GifFlowControl {
private static final String TAG = "GGMainActivity";
static ArrayList<String> listOfFiles = new ArrayList<String>();
static String gifFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
@Override
public void onResume() {
super.onResume();
getFragmentManager().beginTransaction()
.replace(R.id.container, new ImageGrabFrag())
.commit();
}
@Override
public void startBuild() {
getFragmentManager().beginTransaction()
.replace(R.id.container, new GifBuilderFrag())
.commit();
}
@Override
public void startDisplay() {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
public class GifBuilderFrag extends Fragment {
private static final String TAG = "GifBuilderFrag";
private GifFlowControl flowControl;
private ImageView curImg;
private ProgressBar progressBar;
private AsyncTask updater;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
flowControl = (GifFlowControl) activity;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
View rootView = inflater.inflate(R.layout.builder, container, false);
return rootView;
}
@Override
public void onResume() {
super.onResume();
new BackgroundGif().execute();
}
private class BackgroundGif extends AsyncTask<Void, Bitmap, String> {
@Override
protected String doInBackground(Void... params) {
Log.d(TAG, "GlassPhotoDelay");
String gifFile = SimpleFileUtils.getDir() + File.separator + System.currentTimeMillis() + ".gif";
// http://stackoverflow.com/a/20277649/974800
ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();
for (String filename : MainActivity.listOfFiles) {
File f = new File(filename);
if (!f.exists())
continue;
Bitmap b = ImageTools.generatePic(filename);
bitmaps.add(b);
publishProgress(b);
}
byte[] bytes = ImageTools.generateGIF(bitmaps);
SimpleFileUtils.write(gifFile, bytes);
String newGifFile = SimpleFileUtils.getSdDir() + File.separator
+ "DCIM" + File.separator + "Camera" + File.separator
+ System.currentTimeMillis() + ".gif";
try {
SimpleFileUtils.copy(gifFile, newGifFile);
} catch (IOException e) {
e.printStackTrace();
}
for (String filename : MainActivity.listOfFiles) {
File f = new File(filename);
if (f.exists())
f.delete();
}
return gifFile;
}
@Override
protected void onPostExecute(String params) {
flowControl.startDisplay();
}
}
}
public class GifBuilderFrag extends Fragment {
private static final String TAG = "GifBuilderFrag";
private GifFlowControl flowControl;
private ImageView curImg;
private ProgressBar progressBar;
private AsyncTask updater;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
flowControl = (GifFlowControl) activity;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
View rootView = inflater.inflate(R.layout.builder2, container, false);
curImg = (ImageView) rootView.findViewById(R.id.cur_img);
progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);
return rootView;
}
@Override
public void onResume() {
super.onResume();
new BackgroundGif().execute();
updater = new ProgressUpdater().execute();
}
@Override
public void onPause() {
updater.cancel(true);
super.onPause();
}
private class ProgressUpdater extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
int count = 0;
try {
while (true) {
publishProgress((count++ % 100));
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
protected void onProgressUpdate(Integer... progress) {
progressBar.setProgress(progress[0]);
}
}
private class BackgroundGif extends AsyncTask<Void, Bitmap, String> {
@Override
protected String doInBackground(Void... params) {
Log.d(TAG, "GlassPhotoDelay");
String gifFile = SimpleFileUtils.getDir() + File.separator + System.currentTimeMillis() + ".gif";
ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();
for (String filename : GGMainActivity.listOfFiles) {
File f = new File(filename);
if (!f.exists())
continue;
Bitmap b = ImageTools.generatePic(filename);
bitmaps.add(b);
publishProgress(b);
}
byte[] bytes = ImageTools.generateGIF(bitmaps);
SimpleFileUtils.write(gifFile, bytes);
String newGifFile = SimpleFileUtils.getSdDir() + File.separator
+ "DCIM" + File.separator + "Camera" + File.separator
+ System.currentTimeMillis() + ".gif";
try {
SimpleFileUtils.copy(gifFile, newGifFile);
} catch (IOException e) {
e.printStackTrace();
}
for (String filename : GGMainActivity.listOfFiles) {
File f = new File(filename);
if (f.exists())
f.delete();
}
return gifFile;
}
protected void onProgressUpdate(Bitmap... progress) {
curImg.setImageBitmap(progress[0]);
}
@Override
protected void onPostExecute(String params) {
Log.d(TAG, "creating card with params: " + params);
flowControl.startDisplay();
}
}
}
public class GifDisplayFrag extends Fragment {
private static final String TAG = "GifDisplayFrag";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = new GifWebView(getActivity(), GGMainActivity.gifFile, "480", "360");
return v;
}
}
public class GifDisplayFrag extends Fragment {
private static final String TAG = "GifDisplayFrag";
public static final String GOOGLE_DRIVE = "com.google.android.apps.docs";
public static final String GOOGLE_PLUS = "com.google.android.apps.plus";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = new GifWebView(getActivity(), GGMainActivity.gifFile, "480", "360");
new DriveUpload().execute();
return v;
}
private class DriveUpload extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, "GlassPhotoDelay");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void params) {
try {
// http://divingintoglass.blogspot.com/2014/01/how-to-upload-files-using-google-drive.html
Uri imageUri = Uri.fromFile(new File(GGMainActivity.gifFile));
Intent shareIntent = ShareCompat.IntentBuilder.from(getActivity())
.setText("#glassgif #throughglass")
.setType("image/gif")
.setStream(imageUri )
.getIntent()
.setPackage(GOOGLE_DRIVE);
startActivity(shareIntent);
getActivity().finish();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
package com.feigdev.gg_test;
public interface GifFlowControl {
public void startBuild();
public void startDisplay();
}
package com.feigdev.gg_test;
import android.app.Activity;
import android.app.Fragment;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.*;
// requires library project https://github.com/emil10001/ReusableAndroidUtils
import com.feigdev.reusableandroidutils.graphics.PhotoCallback;
import com.feigdev.reusableandroidutils.graphics.PhotoHandler;
import com.google.android.glass.sample.camera.CameraPreview;
import java.io.IOException;
/**
* Created by ejf3 on 3/11/14.
*/
public class ImageGrabFrag extends Fragment implements PhotoCallback {
private static final String TAG = "ImageGrabFrag";
private Camera camera;
private CameraPreview cameraPreview;
private SurfaceView preview;
private SurfaceHolder holder;
private static boolean isAlive = false;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main2, container, false);
preview = (SurfaceView) rootView.findViewById(R.id.preview1);
preview.getHolder().addCallback(mSurfaceHolderCallback);
return rootView;
}
@Override
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
isAlive = true;
}
@Override
public void onPause() {
Log.d(TAG, "onPause");
isAlive = false;
if (camera != null) {
camera.stopPreview();
camera.release();
camera = null;
}
super.onPause();
}
private void initCamera() {
Log.d(TAG, "initCamera");
// do we have a camera?
if (!getActivity().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Log.d(TAG, "No camera on this device");
return;
}
camera = Camera.open();
cameraPreview = new CameraPreview(getActivity());
cameraPreview.setCamera(camera);
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
camera.startPreview();
}
private void takePicture() {
Log.d(TAG, "takePicture");
if (!isAlive)
return;
camera.takePicture(null, null,
new PhotoHandler(this));
}
@Override
public void pictureTaken(String filename){
Log.d(TAG, "captured file " + filename);
getActivity().finish();
}
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder hldr) {
holder = hldr;
initCamera();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Nothing to do here.
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// Nothing to do here.
}
};
}
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
isAlive = true;
new GlassPhotoDelay().execute();
}
private void initCamera() {
Log.d(TAG, "initCamera");
// do we have a camera?
if (!getActivity().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Log.d(TAG, "No camera on this device");
return;
}
camera = Camera.open();
camera.setDisplayOrientation(0);
/**
* The camera preview on Glass needs certain special parameters to run properly
* SO help: http://stackoverflow.com/a/19257078/974800
*/
Camera.Parameters params = camera.getParameters();
params.setPreviewFpsRange(30000, 30000);
params.setJpegQuality(90);
// hard-coding is bad, but I'm a bit lazy
params.setPictureSize(640, 480);
params.setPreviewSize(640, 480);
camera.setParameters(params);
cameraPreview = new CameraPreview(getActivity());
cameraPreview.setCamera(camera);
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
camera.startPreview();
// note we removed the call to takePicture()
}
/**
* There is currently a race condition where using a voice command to launch,
* then trying to grab the camera will fail, because the microphone is still locked
* <p/>
* http://stackoverflow.com/a/20154537/974800
* https://code.google.com/p/google-glass-api/issues/detail?id=259
*/
private class GlassPhotoDelay extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, "GlassPhotoDelay");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void params) {
if (!isAlive)
return;
initCamera();
takePicture();
}
}
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder hldr) {
holder = hldr;
// removed initCamera call
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Nothing to do here.
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// Nothing to do here.
}
};
package com.feigdev.gg_test;
import android.app.Activity;
import android.app.Fragment;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.*;
import com.feigdev.reusableandroidutils.graphics.PhotoCallback;
import com.feigdev.reusableandroidutils.graphics.PhotoHandler;
import com.google.android.glass.sample.camera.CameraPreview;
import java.io.IOException;
/**
* Created by ejf3 on 3/11/14.
*/
public class ImageGrabFrag extends Fragment implements PhotoCallback {
private static final String TAG = "ImageGrabFrag";
private Camera camera;
private CameraPreview cameraPreview;
private SurfaceView preview;
private SurfaceHolder holder;
private int count = 0;
private static boolean isAlive = false;
private GifFlowControl flowControl;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
flowControl = (GifFlowControl)activity;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main2, container, false);
preview = (SurfaceView) rootView.findViewById(R.id.preview1);
preview.getHolder().addCallback(mSurfaceHolderCallback);
return rootView;
}
@Override
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
isAlive = true;
count = 0;
new GlassPhotoDelay().execute();
}
@Override
public void onPause() {
Log.d(TAG, "onPause");
isAlive = false;
if (camera != null) {
camera.stopPreview();
camera.release();
camera = null;
}
super.onPause();
}
private void initCamera() {
Log.d(TAG, "initCamera");
// do we have a camera?
if (!getActivity().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Log.d(TAG, "No camera on this device");
return;
}
camera = Camera.open();
camera.setDisplayOrientation(0);
/**
* The camera preview on Glass needs certain special parameters to run properly
* SO help: http://stackoverflow.com/a/19257078/974800
*/
Camera.Parameters params = camera.getParameters();
params.setPreviewFpsRange(30000, 30000);
params.setJpegQuality(90);
// hard-coding is bad, but I'm a bit lazy
params.setPictureSize(640, 480);
params.setPreviewSize(640, 480);
camera.setParameters(params);
cameraPreview = new CameraPreview(getActivity());
cameraPreview.setCamera(camera);
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
camera.startPreview();
}
private void takePicture() {
Log.d(TAG, "takePicture");
count++;
if (!isAlive)
return;
camera.takePicture(null, null,
new PhotoHandler(this));
}
@Override
public void pictureTaken(String filename){
GGMainActivity.listOfFiles.add(filename);
if (count >= 5){
Log.d(TAG, "taken 5");
flowControl.startBuild();
return;
}
if (!isAlive)
return;
camera.startPreview();
takePicture();
}
/**
* There is currently a race condition where using a voice command to launch,
* then trying to grab the camera will fail, because the microphone is still locked
* <p/>
* http://stackoverflow.com/a/20154537/974800
* https://code.google.com/p/google-glass-api/issues/detail?id=259
*/
private class GlassPhotoDelay extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, "GlassPhotoDelay");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void params) {
if (!isAlive)
return;
initCamera();
takePicture();
}
}
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder hldr) {
holder = hldr;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Nothing to do here.
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// Nothing to do here.
}
};
}
private void initCamera() {
Log.d(TAG, "initCamera");
// do we have a camera?
if (!getActivity().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Log.d(TAG, "No camera on this device");
return;
}
camera = Camera.open();
camera.setDisplayOrientation(0);
/**
* The camera preview on Glass needs certain special parameters to run properly
* SO help: http://stackoverflow.com/a/19257078/974800
*/
Camera.Parameters params = camera.getParameters();
params.setPreviewFpsRange(30000, 30000);
params.setJpegQuality(90);
// hard-coding is bad, but I'm a bit lazy
params.setPictureSize(640, 480);
params.setPreviewSize(640, 480);
camera.setParameters(params);
cameraPreview = new CameraPreview(getActivity());
cameraPreview.setCamera(camera);
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
camera.startPreview();
takePicture();
}
// in BackgroundGif subclass
protected void onPostExecute(String params) {
Log.d(TAG, "creating card with params: " + params);
if (PlatformUtils.isGlass()) {
if (null == params)
return;
// http://stackoverflow.com/a/21843601/974800
Uri imgUri = Uri.fromFile(new File(params));
GGMainActivity.gifFile = params;
// create card
Card gifCard = new Card(getActivity());
if (null != imgUri) {
gifCard.addImage(imgUri);
gifCard.setImageLayout(Card.ImageLayout.FULL);
gifCard.setText(MainActivity.gifFile);
} else
gifCard.setText("failed to get image uri");
// menus currently not supported
// https://code.google.com/p/google-glass-api/issues/detail?id=320
TimelineManager tlm = TimelineManager.from(getActivity());
tlm.insert(gifCard);
Log.d(TAG, "inserted into timeline!");
}
flowControl.startDisplay();
}
static ArrayList<String> listOfFiles = new ArrayList<String>();
private GifFlowControl flowControl;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
flowControl = (GifFlowControl)activity;
}
@Override
public void pictureTaken(String filename){
GGMainActivity.listOfFiles.add(filename);
if (count >= 5){
Log.d(TAG, "taken 5");
// use the interface to do a fragment transaction in the activity
flowControl.startBuild();
return;
}
if (!isAlive)
return;
camera.startPreview();
takePicture();
}
private static int count = 0;
//...
@Override
public void onResume() {
count = 0;
//...
}
private void takePicture() {
Log.d(TAG, "takePicture");
count++;
if (!isAlive)
return;
camera.takePicture(null, null,
new PhotoHandler(this));
}
@Override
public void pictureTaken(String filename){
GGMainActivity.listOfFiles.add(filename);
if (count >= 5){
count = 0;
Log.d(TAG, "taken 5");
getActivity().finish();
return;
}
if (!isAlive)
return;
camera.startPreview();
takePicture();
}
@Override
public void startDisplay() {
// https://www.grokkingandroid.com/adding-files-to-androids-media-library-using-the-mediascanner/
try {
Intent intent =
new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(new File(gifFile)));
sendBroadcast(intent);
} catch (Exception e) {
e.printStackTrace();
}
getFragmentManager().beginTransaction()
.replace(R.id.container, new GifDisplayFrag())
.commit();
}
<string name="voice_launch">launch the app</string>
<?xml version="1.0" encoding="utf-8"?>
<!-- res/xml/voice_launch.xml -->
<trigger keyword="@string/voice_launch">
</trigger>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment