diff --git a/Chapter 02 - Services/SimpleService/.idea/.name b/Chapter 02 - Services/SimpleService/.idea/.name new file mode 100644 index 0000000..fbfcdf8 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/.name @@ -0,0 +1 @@ +SimpleService \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/.idea/checkstyle-idea.xml b/Chapter 02 - Services/SimpleService/.idea/checkstyle-idea.xml new file mode 100644 index 0000000..35ee4f5 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/checkstyle-idea.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/.idea/compiler.xml b/Chapter 02 - Services/SimpleService/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/Chapter 02 - Services/SimpleService/.idea/copyright/profiles_settings.xml b/Chapter 02 - Services/SimpleService/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/.idea/dictionaries/Igor.xml b/Chapter 02 - Services/SimpleService/.idea/dictionaries/Igor.xml new file mode 100644 index 0000000..e1694e4 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/dictionaries/Igor.xml @@ -0,0 +1,8 @@ + + + + advancedandroidbook + simpleservice + + + \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/.idea/encodings.xml b/Chapter 02 - Services/SimpleService/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/Chapter 02 - Services/SimpleService/.idea/findbugs-idea.xml b/Chapter 02 - Services/SimpleService/.idea/findbugs-idea.xml new file mode 100644 index 0000000..3d02d13 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/findbugs-idea.xml @@ -0,0 +1,222 @@ + + + + + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/.idea/gradle.xml b/Chapter 02 - Services/SimpleService/.idea/gradle.xml new file mode 100644 index 0000000..736c7b5 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/.idea/libraries/support_annotations_21_0_3.xml b/Chapter 02 - Services/SimpleService/.idea/libraries/support_annotations_21_0_3.xml new file mode 100644 index 0000000..7c7eed4 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/libraries/support_annotations_21_0_3.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/.idea/libraries/support_v4_21_0_3.xml b/Chapter 02 - Services/SimpleService/.idea/libraries/support_v4_21_0_3.xml new file mode 100644 index 0000000..4f716bf --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/libraries/support_v4_21_0_3.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/.idea/misc.xml b/Chapter 02 - Services/SimpleService/.idea/misc.xml new file mode 100644 index 0000000..df81b8d --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/misc.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + Android API 21 Platform + + + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/.idea/modules.xml b/Chapter 02 - Services/SimpleService/.idea/modules.xml new file mode 100644 index 0000000..5859a2f --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/.idea/scopes/scope_settings.xml b/Chapter 02 - Services/SimpleService/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/.idea/vcs.xml b/Chapter 02 - Services/SimpleService/.idea/vcs.xml new file mode 100644 index 0000000..def6a6a --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/.idea/workspace.xml b/Chapter 02 - Services/SimpleService/.idea/workspace.xml new file mode 100644 index 0000000..65d2d23 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/.idea/workspace.xml @@ -0,0 +1,1754 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + + + + + + + 1425301741483 + 1425301741483 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file://$PROJECT_DIR$/app/src/main/java/com/advancedandroidbook/simpleservice/GPXService.java + 115 + + + + file://$PROJECT_DIR$/app/src/main/java/com/advancedandroidbook/simpleservice/ServiceControlActivity.java + 99 + + + + file://$PROJECT_DIR$/app/src/main/java/com/advancedandroidbook/simpleservice/ServiceControlActivity.java + 75 + + + + file://$PROJECT_DIR$/app/src/main/java/com/advancedandroidbook/simpleservice/GPXService.java + 180 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/SimpleService.iml b/Chapter 02 - Services/SimpleService/SimpleService.iml new file mode 100644 index 0000000..ace71b8 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/SimpleService.iml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/app/app.iml b/Chapter 02 - Services/SimpleService/app/app.iml new file mode 100644 index 0000000..c0bbed0 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/app/app.iml @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter 02 - Services/SimpleService/app/build.gradle b/Chapter 02 - Services/SimpleService/app/build.gradle new file mode 100644 index 0000000..39c4258 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/app/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'com.android.application' +android { + compileSdkVersion 21 + buildToolsVersion "21.1.2" + + defaultConfig { + applicationId "com.advancedandroidbook.simpleservice" + minSdkVersion 16 + targetSdkVersion 21 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} +dependencies { + compile 'com.android.support:support-v4:21.0.3' +} \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/app/src/main/AndroidManifest.xml b/Chapter 02 - Services/SimpleService/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ae4008b --- /dev/null +++ b/Chapter 02 - Services/SimpleService/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/GPXPoint.aidl b/Chapter 02 - Services/SimpleService/app/src/main/aidl/com/advancedandroidbook/simpleservice/GPXPoint.aidl similarity index 100% rename from Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/GPXPoint.aidl rename to Chapter 02 - Services/SimpleService/app/src/main/aidl/com/advancedandroidbook/simpleservice/GPXPoint.aidl diff --git a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl b/Chapter 02 - Services/SimpleService/app/src/main/aidl/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl similarity index 100% rename from Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl rename to Chapter 02 - Services/SimpleService/app/src/main/aidl/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl diff --git a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/GPXPoint.java b/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXPoint.java similarity index 100% rename from Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/GPXPoint.java rename to Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXPoint.java diff --git a/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXService.java b/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXService.java new file mode 100644 index 0000000..9215bba --- /dev/null +++ b/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXService.java @@ -0,0 +1,197 @@ +package com.advancedandroidbook.simpleservice; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.widget.Toast; + +import java.util.Date; +import java.util.Locale; + +public class GPXService extends Service { + private static final int GPS_NOTIFY = 0x2001; + private static final String DEBUG_TAG = "GPXService"; + public static final String EXTRA_UPDATE_RATE = "update-rate"; + public static final String GPX_SERVICE = "com.advancedandroidbook.GPXService.SERVICE"; + private LocationManager location = null; + private NotificationManager notifier = null; + private int updateRate = -1; + + @Override + public void onCreate() { + super.onCreate(); + location = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + notifier = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // Android 2.0, L5, version + Log.v(DEBUG_TAG, "onStartCommand() called, must be on L5 or later"); + if (flags != 0) { + Log.w(DEBUG_TAG, "Redelivered or retrying service start: " + flags); + } + doServiceStart(intent, startId); + return Service.START_REDELIVER_INTENT; + } + + private void doServiceStart(Intent intent, int startId) { + updateRate = intent.getIntExtra(EXTRA_UPDATE_RATE, -1); + if (updateRate == -1) { + updateRate = 60000; + } + Criteria criteria = new Criteria(); + criteria.setAccuracy(Criteria.NO_REQUIREMENT); + criteria.setPowerRequirement(Criteria.POWER_LOW); + location = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + String best = location.getBestProvider(criteria, true); + location.requestLocationUpdates(best, updateRate, 0, trackListener); + // notify that we've started up + Intent toLaunch = new Intent(getApplicationContext(), + ServiceControlActivity.class); + PendingIntent intentBack = PendingIntent.getActivity( + getApplicationContext(), 0, toLaunch, 0); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); + builder.setTicker("Builder GPS Tracking"); + builder.setSmallIcon(android.R.drawable.stat_notify_more); + builder.setWhen(System.currentTimeMillis()); + builder.setContentTitle("Builder GPS Tracking"); + builder.setContentText("Tracking start at " + updateRate + "ms intervals with [" + + best + "] as the provider."); + builder.setContentIntent(intentBack); + builder.setAutoCancel(true); + Notification notify = builder.build(); + + notifier.notify(GPS_NOTIFY, notify); + } + + @Override + public void onDestroy() { + Log.v(DEBUG_TAG, "onDestroy() called"); + if (location != null) { + location.removeUpdates(trackListener); + location = null; + } + // notify that we've stopped + Intent toLaunch = new Intent(getApplicationContext(), + ServiceControlActivity.class); + PendingIntent intentBack = PendingIntent.getActivity( + getApplicationContext(), 0, toLaunch, 0); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); + builder.setTicker("Builder GPS Tracking"); + builder.setSmallIcon(android.R.drawable.stat_notify_more); + builder.setWhen(System.currentTimeMillis()); + builder.setContentTitle("Builder GPS Tracking"); + builder.setContentText("Tracking stopped"); + builder.setContentIntent(intentBack); + builder.setAutoCancel(true); + Notification notify = builder.build(); + + notifier.notify(GPS_NOTIFY, notify); + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + // we only have one, so no need to check the intent + return mRemoteInterfaceBinder; + } + + // our remote interface + private final IRemoteInterface.Stub mRemoteInterfaceBinder = new IRemoteInterface.Stub() { + public Location getLastLocation() { + Log.v("interface", "getLastLocation() called"); + return lastLocation; + } + + public GPXPoint getGPXPoint() { + if (lastLocation == null) { + return null; + } else { + Log.v("interface", "getGPXPoint() called"); + GPXPoint point = new GPXPoint(); + point.elevation = lastLocation.getAltitude(); + point.latitude = (int) (lastLocation.getLatitude() * 1E6); + point.longitude = (int) (lastLocation.getLongitude() * 1E6); + point.timestamp = new Date(lastLocation.getTime()); + return point; + } + } + }; + + private Location firstLocation = null; + private Location lastLocation = null; + private long lastTime = -1; + private long firstTime = -1; + + private LocationListener trackListener = new LocationListener() { + public void onLocationChanged(Location location) { + long thisTime = System.currentTimeMillis(); + long diffTime = thisTime - lastTime; + Log.v(DEBUG_TAG, "diffTime == " + diffTime + ", updateRate = " + + updateRate); + if (diffTime < updateRate) { + // it hasn't been long enough yet + return; + } + lastTime = thisTime; + String locInfo = String.format( + Locale.getDefault(), + "Current loc = (%f, %f) @ (%.1f meters up)", + location.getLatitude(), location.getLongitude(), + location.getAltitude()); + if (lastLocation != null) { + float distance = location.distanceTo(lastLocation); + locInfo += String.format("\n Distance from last = %.1f meters", + distance); + float lastSpeed = distance / diffTime; + locInfo += String.format("\n\tSpeed: %.1fm/s", lastSpeed); + if (location.hasSpeed()) { + float gpsSpeed = location.getSpeed(); + locInfo += String.format(" (or %.1fm/s)", lastSpeed, + gpsSpeed); + } else { + } + } + if (firstLocation != null && firstTime != -1) { + float overallDistance = location.distanceTo(firstLocation); + float overallSpeed = overallDistance / (thisTime - firstTime); + locInfo += String.format( + "\n\tOverall speed: %.1fm/s over %.1f meters", + overallSpeed, overallDistance); + } + lastLocation = location; + if (firstLocation == null) { + firstLocation = location; + firstTime = thisTime; + } + Toast.makeText(getApplicationContext(), locInfo, Toast.LENGTH_LONG) + .show(); + Log.v(DEBUG_TAG, "Test time"); + } + + public void onProviderDisabled(String provider) { + Log.v(DEBUG_TAG, "Provider disabled " + provider); + } + + public void onProviderEnabled(String provider) { + Log.v(DEBUG_TAG, "Provider enabled " + provider); + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; +} diff --git a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/MenuActivity.java b/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/MenuActivity.java similarity index 100% rename from Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/MenuActivity.java rename to Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/MenuActivity.java diff --git a/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/ServiceControlActivity.java b/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/ServiceControlActivity.java new file mode 100644 index 0000000..e60da57 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/ServiceControlActivity.java @@ -0,0 +1,109 @@ +package com.advancedandroidbook.simpleservice; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.Location; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +public class ServiceControlActivity extends Activity implements ServiceConnection { + IRemoteInterface mRemoteInterface = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.control); + + final TextView status = (TextView) findViewById(R.id.status); + + Button go = (Button) findViewById(R.id.go); + go.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Intent service = new Intent(GPXService.GPX_SERVICE); + service.setPackage("com.advancedandroidbook.simpleservice"); + service.putExtra(GPXService.EXTRA_UPDATE_RATE, 5000); + startService(service); + } + }); + + Button stop = (Button) findViewById(R.id.stop); + stop.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + releaseBind(); + stopService(new Intent(GPXService.GPX_SERVICE)); + } + }); + + Button getLastLoc = (Button) findViewById(R.id.get_last); + getLastLoc.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + try { + String info = "Info from remote: \n"; + Location loc = mRemoteInterface.getLastLocation(); + if (loc != null) { + double lat = loc.getLatitude(); + double lon = loc.getLongitude(); + info += String.format("Last location = (%f, %f)\n", + lat, lon); + } else { + info += "No last location yet.\n"; + } + GPXPoint point = mRemoteInterface.getGPXPoint(); + if (point != null) { + info += String + .format("GPX point = (%d, %d) @ (%.1f meters) @ (%s)\n", + point.latitude, point.longitude, + point.elevation, + point.timestamp.toString()); + } + status.setText(info); + } catch (RemoteException e) { + Log.e("ServiceControl", "Call to remote interface failed.", e); + } + } + }); + } + + public void onServiceConnected(ComponentName name, IBinder service) { + mRemoteInterface = IRemoteInterface.Stub.asInterface(service); + Log.v("ServiceControl", "Interface bound."); + Button getLastLoc = (Button) findViewById(R.id.get_last); + getLastLoc.setVisibility(View.VISIBLE); + } + + public void onServiceDisconnected(ComponentName name) { + mRemoteInterface = null; + Button getLastLoc = (Button) findViewById(R.id.get_last); + getLastLoc.setVisibility(View.GONE); + Log.v("ServiceControl", "Remote interface no longer bound"); + } + + public void releaseBind() { + unbindService(this); + } + + @Override + protected void onResume() { + super.onResume(); + // get a link to our remote service + Intent intent = new Intent(IRemoteInterface.class.getName()); + intent.setPackage("com.advancedandroidbook.simpleservice"); + + bindService(intent, this, Context.BIND_AUTO_CREATE); + } + + @Override + protected void onPause() { + // remove the link to the remote service +// releaseBind(); + super.onPause(); + } +} \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/SimpleServiceActivity.java b/Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/SimpleServiceActivity.java similarity index 100% rename from Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/SimpleServiceActivity.java rename to Chapter 02 - Services/SimpleService/app/src/main/java/com/advancedandroidbook/simpleservice/SimpleServiceActivity.java diff --git a/Chapter 02 - Services/SimpleService/res/drawable-hdpi/icon.png b/Chapter 02 - Services/SimpleService/app/src/main/res/drawable-hdpi/icon.png similarity index 100% rename from Chapter 02 - Services/SimpleService/res/drawable-hdpi/icon.png rename to Chapter 02 - Services/SimpleService/app/src/main/res/drawable-hdpi/icon.png diff --git a/Chapter 02 - Services/SimpleService/res/drawable-ldpi/icon.png b/Chapter 02 - Services/SimpleService/app/src/main/res/drawable-ldpi/icon.png similarity index 100% rename from Chapter 02 - Services/SimpleService/res/drawable-ldpi/icon.png rename to Chapter 02 - Services/SimpleService/app/src/main/res/drawable-ldpi/icon.png diff --git a/Chapter 02 - Services/SimpleService/res/drawable-mdpi/icon.png b/Chapter 02 - Services/SimpleService/app/src/main/res/drawable-mdpi/icon.png similarity index 100% rename from Chapter 02 - Services/SimpleService/res/drawable-mdpi/icon.png rename to Chapter 02 - Services/SimpleService/app/src/main/res/drawable-mdpi/icon.png diff --git a/Chapter 02 - Services/SimpleService/res/layout/control.xml b/Chapter 02 - Services/SimpleService/app/src/main/res/layout/control.xml similarity index 100% rename from Chapter 02 - Services/SimpleService/res/layout/control.xml rename to Chapter 02 - Services/SimpleService/app/src/main/res/layout/control.xml diff --git a/Chapter 02 - Services/SimpleService/res/values/strings.xml b/Chapter 02 - Services/SimpleService/app/src/main/res/values/strings.xml similarity index 100% rename from Chapter 02 - Services/SimpleService/res/values/strings.xml rename to Chapter 02 - Services/SimpleService/app/src/main/res/values/strings.xml diff --git a/Chapter 02 - Services/SimpleService/build.gradle b/Chapter 02 - Services/SimpleService/build.gradle new file mode 100644 index 0000000..435d1da --- /dev/null +++ b/Chapter 02 - Services/SimpleService/build.gradle @@ -0,0 +1,14 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.1.0' + } +} +allprojects { + repositories { + jcenter() + } +} \ No newline at end of file diff --git a/Chapter 02 - Services/SimpleService/gradle/wrapper/gradle-wrapper.jar b/Chapter 02 - Services/SimpleService/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8c0fb64 Binary files /dev/null and b/Chapter 02 - Services/SimpleService/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Chapter 02 - Services/SimpleService/gradle/wrapper/gradle-wrapper.properties b/Chapter 02 - Services/SimpleService/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0c71e76 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/Chapter 02 - Services/SimpleService/gradlew b/Chapter 02 - Services/SimpleService/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/Chapter 02 - Services/SimpleService/gradlew.bat b/Chapter 02 - Services/SimpleService/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Chapter 02 - Services/SimpleService/import-summary.txt b/Chapter 02 - Services/SimpleService/import-summary.txt new file mode 100644 index 0000000..ff71d91 --- /dev/null +++ b/Chapter 02 - Services/SimpleService/import-summary.txt @@ -0,0 +1,109 @@ +ECLIPSE ANDROID PROJECT IMPORT SUMMARY +====================================== + +Risky Project Location: +----------------------- +The tools *should* handle project locations in any directory. However, +due to bugs, placing projects in directories containing spaces in the +path, or characters like ", ' and &, have had issues. We're working to +eliminate these bugs, but to save yourself headaches you may want to +move your project to a location where this is not a problem. +D:\git\BOOKS\advancedAndroid4e\Chapter 02 - Services\SimpleService + - - - + +Ignored Files: +-------------- +The following files were *not* copied into the new Gradle project; you +should evaluate whether these are still needed in your project and if +so manually move them: + +* .idea\ +* .idea\.name +* .idea\compiler.xml +* .idea\copyright\ +* .idea\copyright\profiles_settings.xml +* .idea\encodings.xml +* .idea\findbugs-idea.xml +* .idea\misc.xml +* .idea\modules.xml +* .idea\scopes\ +* .idea\scopes\scope_settings.xml +* .idea\vcs.xml +* .idea\workspace.xml +* app\ +* app\build.gradle +* app\src\ +* app\src\main\ +* app\src\main\AndroidManifest.xml +* app\src\main\aidl\ +* app\src\main\aidl\com\ +* app\src\main\aidl\com\advancedandroidbook\ +* app\src\main\aidl\com\advancedandroidbook\simpleservice\ +* app\src\main\aidl\com\advancedandroidbook\simpleservice\GPXPoint.aidl +* app\src\main\aidl\com\advancedandroidbook\simpleservice\IRemoteInterface.aidl +* app\src\main\java\ +* app\src\main\java\com\ +* app\src\main\java\com\advancedandroidbook\ +* app\src\main\java\com\advancedandroidbook\simpleservice\ +* app\src\main\java\com\advancedandroidbook\simpleservice\GPXPoint.java +* app\src\main\java\com\advancedandroidbook\simpleservice\GPXService.java +* app\src\main\java\com\advancedandroidbook\simpleservice\MenuActivity.java +* app\src\main\java\com\advancedandroidbook\simpleservice\ServiceControlActivity.java +* app\src\main\java\com\advancedandroidbook\simpleservice\SimpleServiceActivity.java +* app\src\main\res\ +* app\src\main\res\drawable-hdpi\ +* app\src\main\res\drawable-hdpi\icon.png +* app\src\main\res\drawable-ldpi\ +* app\src\main\res\drawable-ldpi\icon.png +* app\src\main\res\drawable-mdpi\ +* app\src\main\res\drawable-mdpi\icon.png +* app\src\main\res\layout\ +* app\src\main\res\layout\control.xml +* app\src\main\res\values\ +* app\src\main\res\values\strings.xml +* build.gradle +* gradle\ +* gradle\wrapper\ +* gradle\wrapper\gradle-wrapper.jar +* gradle\wrapper\gradle-wrapper.properties +* gradlew +* gradlew.bat +* settings.gradle + +Replaced Jars with Dependencies: +-------------------------------- +The importer recognized the following .jar files as third party +libraries and replaced them with Gradle dependencies instead. This has +the advantage that more explicit version information is known, and the +libraries can be updated automatically. However, it is possible that +the .jar file in your project was of an older version than the +dependency we picked, which could render the project not compileable. +You can disable the jar replacement in the import wizard and try again: + +android-support-v4.jar => com.android.support:support-v4:19.1.0 + +Moved Files: +------------ +Android Gradle projects use a different directory structure than ADT +Eclipse projects. Here's how the projects were restructured: + +* AndroidManifest.xml => app\src\main\AndroidManifest.xml +* res\ => app\src\main\res\ +* src\ => app\src\main\java\ +* src\com\advancedandroidbook\simpleservice\GPXPoint.aidl => app\src\main\aidl\com\advancedandroidbook\simpleservice\GPXPoint.aidl +* src\com\advancedandroidbook\simpleservice\IRemoteInterface.aidl => app\src\main\aidl\com\advancedandroidbook\simpleservice\IRemoteInterface.aidl + +Next Steps: +----------- +You can now build the project. The Gradle project needs network +connectivity to download dependencies. + +Bugs: +----- +If for some reason your project does not build, and you determine that +it is due to a bug or limitation of the Eclipse to Gradle importer, +please file a bug at http://b.android.com with category +Component-Tools. + +(This import summary is for your information only, and can be deleted +after import once you are satisfied with the results.) diff --git a/Chapter 02 - Services/SimpleService/libs/android-support-v4.jar b/Chapter 02 - Services/SimpleService/libs/android-support-v4.jar deleted file mode 100644 index 9056828..0000000 Binary files a/Chapter 02 - Services/SimpleService/libs/android-support-v4.jar and /dev/null differ diff --git a/Chapter 02 - Services/SimpleService/project.properties b/Chapter 02 - Services/SimpleService/project.properties deleted file mode 100644 index 4ab1256..0000000 --- a/Chapter 02 - Services/SimpleService/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-19 diff --git a/Chapter 02 - Services/SimpleService/settings.gradle b/Chapter 02 - Services/SimpleService/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/Chapter 02 - Services/SimpleService/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/GPXService.java b/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/GPXService.java deleted file mode 100644 index 3af9806..0000000 --- a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/GPXService.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.advancedandroidbook.simpleservice; - -import java.util.Date; -import java.util.Locale; - -import com.advancedandroidbook.simpleservice.IRemoteInterface; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.location.Criteria; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.os.Bundle; -import android.os.IBinder; -import android.support.v4.app.NotificationCompat; -import android.util.Log; -import android.widget.Toast; - -public class GPXService extends Service { - private static final int GPS_NOTIFY = 0x2001; - private static final String DEBUG_TAG = "GPXService"; - public static final String EXTRA_UPDATE_RATE = "update-rate"; - public static final String GPX_SERVICE = "com.advancedandroidbook.GPXService.SERVICE"; - private LocationManager location = null; - private NotificationManager notifier = null; - private int updateRate = -1; - - @Override - public void onCreate() { - super.onCreate(); - location = (LocationManager) getSystemService(Context.LOCATION_SERVICE); - notifier = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // Android 2.0, L5, version - Log.v(DEBUG_TAG, "onStartCommand() called, must be on L5 or later"); - if (flags != 0) { - Log.w(DEBUG_TAG, "Redelivered or retrying service start: " + flags); - } - doServiceStart(intent, startId); - return Service.START_REDELIVER_INTENT; - } - - @Override - public void onStart(Intent intent, int startId) { - // Pre Android 2.0 version - super.onStart(intent, startId); - Log.v(DEBUG_TAG, "onStart() called, must be on L3 or L4"); - doServiceStart(intent, startId); - } - - private void doServiceStart(Intent intent, int startId) { - updateRate = intent.getIntExtra(EXTRA_UPDATE_RATE, -1); - if (updateRate == -1) { - updateRate = 60000; - } - Criteria criteria = new Criteria(); - criteria.setAccuracy(Criteria.NO_REQUIREMENT); - criteria.setPowerRequirement(Criteria.POWER_LOW); - location = (LocationManager) getSystemService(Context.LOCATION_SERVICE); - String best = location.getBestProvider(criteria, true); - location.requestLocationUpdates(best, updateRate, 0, trackListener); - // notify that we've started up - Intent toLaunch = new Intent(getApplicationContext(), - ServiceControlActivity.class); - PendingIntent intentBack = PendingIntent.getActivity( - getApplicationContext(), 0, toLaunch, 0); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); - builder.setTicker("Builder GPS Tracking"); - builder.setSmallIcon(android.R.drawable.stat_notify_more); - builder.setWhen(System.currentTimeMillis()); - builder.setContentTitle("Builder GPS Tracking"); - builder.setContentText("Tracking start at " + updateRate + "ms intervals with [" - + best + "] as the provider."); - builder.setContentIntent(intentBack); - builder.setAutoCancel(true); - Notification notify = builder.build(); - - notifier.notify(GPS_NOTIFY, notify); - } - - @Override - public void onDestroy() { - Log.v(DEBUG_TAG, "onDestroy() called"); - if (location != null) { - location.removeUpdates(trackListener); - location = null; - } - // notify that we've stopped - Intent toLaunch = new Intent(getApplicationContext(), - ServiceControlActivity.class); - PendingIntent intentBack = PendingIntent.getActivity( - getApplicationContext(), 0, toLaunch, 0); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); - builder.setTicker("Builder GPS Tracking"); - builder.setSmallIcon(android.R.drawable.stat_notify_more); - builder.setWhen(System.currentTimeMillis()); - builder.setContentTitle("Builder GPS Tracking"); - builder.setContentText("Tracking stopped"); - builder.setContentIntent(intentBack); - builder.setAutoCancel(true); - Notification notify = builder.build(); - - notifier.notify(GPS_NOTIFY, notify); - super.onDestroy(); - } - - @Override - public IBinder onBind(Intent intent) { - // we only have one, so no need to check the intent - return mRemoteInterfaceBinder; - } - - // our remote interface - private final IRemoteInterface.Stub mRemoteInterfaceBinder = new IRemoteInterface.Stub() { - public Location getLastLocation() { - Log.v("interface", "getLastLocation() called"); - return lastLocation; - } - - public GPXPoint getGPXPoint() { - if (lastLocation == null) { - return null; - } else { - Log.v("interface", "getGPXPoint() called"); - GPXPoint point = new GPXPoint(); - point.elevation = lastLocation.getAltitude(); - point.latitude = (int) (lastLocation.getLatitude() * 1E6); - point.longitude = (int) (lastLocation.getLongitude() * 1E6); - point.timestamp = new Date(lastLocation.getTime()); - return point; - } - } - }; - - private Location firstLocation = null; - private Location lastLocation = null; - private long lastTime = -1; - private long firstTime = -1; - - private LocationListener trackListener = new LocationListener() { - public void onLocationChanged(Location location) { - long thisTime = System.currentTimeMillis(); - long diffTime = thisTime - lastTime; - Log.v(DEBUG_TAG, "diffTime == " + diffTime + ", updateRate = " - + updateRate); - if (diffTime < updateRate) { - // it hasn't been long enough yet - return; - } - lastTime = thisTime; - String locInfo = String.format( - Locale.getDefault(), - "Current loc = (%f, %f) @ (%.1f meters up)", - location.getLatitude(), location.getLongitude(), - location.getAltitude()); - if (lastLocation != null) { - float distance = location.distanceTo(lastLocation); - locInfo += String.format("\n Distance from last = %.1f meters", - distance); - float lastSpeed = distance / diffTime; - locInfo += String.format("\n\tSpeed: %.1fm/s", lastSpeed); - if (location.hasSpeed()) { - float gpsSpeed = location.getSpeed(); - locInfo += String.format(" (or %.1fm/s)", lastSpeed, - gpsSpeed); - } else { - } - } - if (firstLocation != null && firstTime != -1) { - float overallDistance = location.distanceTo(firstLocation); - float overallSpeed = overallDistance / (thisTime - firstTime); - locInfo += String.format( - "\n\tOverall speed: %.1fm/s over %.1f meters", - overallSpeed, overallDistance); - } - lastLocation = location; - if (firstLocation == null) { - firstLocation = location; - firstTime = thisTime; - } - Toast.makeText(getApplicationContext(), locInfo, Toast.LENGTH_LONG) - .show(); - Log.v(DEBUG_TAG, "Test time"); - } - - public void onProviderDisabled(String provider) { - Log.v(DEBUG_TAG, "Provider disabled " + provider); - } - - public void onProviderEnabled(String provider) { - Log.v(DEBUG_TAG, "Provider enabled " + provider); - } - - public void onStatusChanged(String provider, int status, Bundle extras) {} - }; -} diff --git a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/ServiceControlActivity.java b/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/ServiceControlActivity.java deleted file mode 100644 index 2119712..0000000 --- a/Chapter 02 - Services/SimpleService/src/com/advancedandroidbook/simpleservice/ServiceControlActivity.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.advancedandroidbook.simpleservice; - -import com.advancedandroidbook.simpleservice.IRemoteInterface; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.location.Location; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -public class ServiceControlActivity extends Activity implements - ServiceConnection { - IRemoteInterface mRemoteInterface = null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.control); - - final TextView status = (TextView) findViewById(R.id.status); - - Button go = (Button) findViewById(R.id.go); - go.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - Intent service = new Intent(GPXService.GPX_SERVICE); - service.putExtra(GPXService.EXTRA_UPDATE_RATE, 5000); - startService(service); - } - }); - - Button stop = (Button) findViewById(R.id.stop); - stop.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - releaseBind(); - stopService(new Intent(GPXService.GPX_SERVICE)); - } - }); - - Button getLastLoc = (Button) findViewById(R.id.get_last); - getLastLoc.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - try { - String info = "Info from remote: \n"; - Location loc = mRemoteInterface.getLastLocation(); - if (loc != null) { - double lat = loc.getLatitude(); - double lon = loc.getLongitude(); - info += String.format("Last location = (%f, %f)\n", - lat, lon); - } else { - info += "No last location yet.\n"; - } - GPXPoint point = mRemoteInterface.getGPXPoint(); - if (point != null) { - info += String - .format("GPX point = (%d, %d) @ (%.1f meters) @ (%s)\n", - point.latitude, point.longitude, - point.elevation, - point.timestamp.toString()); - } - status.setText(info); - } catch (RemoteException e) { - Log.e("ServiceControl", "Call to remote interface failed.", e); - } - } - }); - } - - public void onServiceConnected(ComponentName name, IBinder service) { - mRemoteInterface = IRemoteInterface.Stub.asInterface(service); - Log.v("ServiceControl", "Interface bound."); - Button getLastLoc = (Button) findViewById(R.id.get_last); - getLastLoc.setVisibility(View.VISIBLE); - } - - public void onServiceDisconnected(ComponentName name) { - mRemoteInterface = null; - Button getLastLoc = (Button) findViewById(R.id.get_last); - getLastLoc.setVisibility(View.GONE); - Log.v("ServiceControl", "Remote interface no longer bound"); - } - - public void releaseBind() { - unbindService(this); - } - - @Override - protected void onResume() { - super.onResume(); - // get a link to our remote service - bindService(new Intent(IRemoteInterface.class.getName()), this, - Context.BIND_AUTO_CREATE); - } - - @Override - protected void onPause() { - // remove the link to the remote service -// releaseBind(); - super.onPause(); - } -} \ No newline at end of file diff --git a/Chapter 02 - Services/UseService/.gitignore b/Chapter 02 - Services/UseService/.gitignore new file mode 100644 index 0000000..03eee2a --- /dev/null +++ b/Chapter 02 - Services/UseService/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +.idea +*.iml +.DS_Store +build diff --git a/Chapter 02 - Services/UseService/AndroidManifest.xml b/Chapter 02 - Services/UseService/AndroidManifest.xml deleted file mode 100644 index 56141b2..0000000 --- a/Chapter 02 - Services/UseService/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Chapter 02 - Services/UseService/app/build.gradle b/Chapter 02 - Services/UseService/app/build.gradle new file mode 100644 index 0000000..dadc4d4 --- /dev/null +++ b/Chapter 02 - Services/UseService/app/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 21 + buildToolsVersion "21.1.2" + + defaultConfig { + applicationId "com.advancedandroidbook.simpleservice" + minSdkVersion 16 + targetSdkVersion 21 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile 'com.android.support:support-v4:21.0.3' +} diff --git a/Chapter 02 - Services/SimpleService/AndroidManifest.xml b/Chapter 02 - Services/UseService/app/src/main/AndroidManifest.xml similarity index 81% rename from Chapter 02 - Services/SimpleService/AndroidManifest.xml rename to Chapter 02 - Services/UseService/app/src/main/AndroidManifest.xml index 84d1906..1768f1c 100644 --- a/Chapter 02 - Services/SimpleService/AndroidManifest.xml +++ b/Chapter 02 - Services/UseService/app/src/main/AndroidManifest.xml @@ -2,21 +2,17 @@ - - + android:versionName="1.0"> + android:label="@string/app_name"> + android:label="@string/app_name"> @@ -27,7 +23,7 @@ + android:enabled="true"> diff --git a/Chapter 02 - Services/UseService/src/com/advancedandroidbook/simpleservice/GPXPoint.aidl b/Chapter 02 - Services/UseService/app/src/main/aidl/com/advancedandroidbook/simpleservice/GPXPoint.aidl similarity index 100% rename from Chapter 02 - Services/UseService/src/com/advancedandroidbook/simpleservice/GPXPoint.aidl rename to Chapter 02 - Services/UseService/app/src/main/aidl/com/advancedandroidbook/simpleservice/GPXPoint.aidl diff --git a/Chapter 02 - Services/UseService/src/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl b/Chapter 02 - Services/UseService/app/src/main/aidl/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl similarity index 98% rename from Chapter 02 - Services/UseService/src/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl rename to Chapter 02 - Services/UseService/app/src/main/aidl/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl index e0438b7..8749b17 100644 --- a/Chapter 02 - Services/UseService/src/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl +++ b/Chapter 02 - Services/UseService/app/src/main/aidl/com/advancedandroidbook/simpleservice/IRemoteInterface.aidl @@ -2,7 +2,9 @@ package com.advancedandroidbook.simpleservice; import com.advancedandroidbook.simpleservice.GPXPoint; + interface IRemoteInterface { + Location getLastLocation(); GPXPoint getGPXPoint(); } diff --git a/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXPoint.java b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXPoint.java new file mode 100644 index 0000000..725c7f3 --- /dev/null +++ b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXPoint.java @@ -0,0 +1,54 @@ +package com.advancedandroidbook.simpleservice; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Date; + +public final class GPXPoint implements Parcelable { + + public int latitude; + public int longitude; + public Date timestamp; + public double elevation; + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + public GPXPoint createFromParcel(Parcel src) { + return new GPXPoint(src); + } + + public GPXPoint[] newArray(int size) { + return new GPXPoint[size]; + } + + }; + + public GPXPoint() { + } + + private GPXPoint(Parcel src) { + readFromParcel(src); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(latitude); + dest.writeInt(longitude); + dest.writeDouble(elevation); + dest.writeLong(timestamp.getTime()); + } + + public void readFromParcel(Parcel src) { + latitude = src.readInt(); + longitude = src.readInt(); + + elevation = src.readDouble(); + timestamp = new Date(src.readLong()); + } + + public int describeContents() { + // nothing special + return 0; + } + +} diff --git a/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXService.java b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXService.java new file mode 100644 index 0000000..e1c7bd7 --- /dev/null +++ b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/GPXService.java @@ -0,0 +1,205 @@ +package com.advancedandroidbook.simpleservice; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.widget.Toast; + +import java.util.Date; +import java.util.Locale; + +public class GPXService extends Service { + private static final int GPS_NOTIFY = 0x2001; + private static final String DEBUG_TAG = "GPXService"; + public static final String EXTRA_UPDATE_RATE = "update-rate"; + public static final String GPX_SERVICE = "com.advancedandroidbook.GPXService.SERVICE"; + private LocationManager location = null; + private NotificationManager notifier = null; + private int updateRate = -1; + + @Override + public void onCreate() { + super.onCreate(); + location = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + notifier = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // Android 2.0, L5, version + Log.v(DEBUG_TAG, "onStartCommand() called, must be on L5 or later"); + if (flags != 0) { + Log.w(DEBUG_TAG, "Redelivered or retrying service start: " + flags); + } + doServiceStart(intent, startId); + return Service.START_REDELIVER_INTENT; + } + + @Override + public void onStart(Intent intent, int startId) { + // Pre Android 2.0 version + super.onStart(intent, startId); + Log.v(DEBUG_TAG, "onStart() called, must be on L3 or L4"); + doServiceStart(intent, startId); + } + + private void doServiceStart(Intent intent, int startId) { + updateRate = intent.getIntExtra(EXTRA_UPDATE_RATE, -1); + if (updateRate == -1) { + updateRate = 60000; + } + Criteria criteria = new Criteria(); + criteria.setAccuracy(Criteria.NO_REQUIREMENT); + criteria.setPowerRequirement(Criteria.POWER_LOW); + location = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + String best = location.getBestProvider(criteria, true); + location.requestLocationUpdates(best, updateRate, 0, trackListener); + // notify that we've started up + Intent toLaunch = new Intent(getApplicationContext(), + ServiceControlActivity.class); + PendingIntent intentBack = PendingIntent.getActivity( + getApplicationContext(), 0, toLaunch, 0); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); + builder.setTicker("Builder GPS Tracking"); + builder.setSmallIcon(android.R.drawable.stat_notify_more); + builder.setWhen(System.currentTimeMillis()); + builder.setContentTitle("Builder GPS Tracking"); + builder.setContentText("Tracking start at " + updateRate + "ms intervals with [" + + best + "] as the provider."); + builder.setContentIntent(intentBack); + builder.setAutoCancel(true); + Notification notify = builder.build(); + + notifier.notify(GPS_NOTIFY, notify); + } + + @Override + public void onDestroy() { + Log.v(DEBUG_TAG, "onDestroy() called"); + if (location != null) { + location.removeUpdates(trackListener); + location = null; + } + // notify that we've stopped + Intent toLaunch = new Intent(getApplicationContext(), + ServiceControlActivity.class); + PendingIntent intentBack = PendingIntent.getActivity( + getApplicationContext(), 0, toLaunch, 0); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); + builder.setTicker("Builder GPS Tracking"); + builder.setSmallIcon(android.R.drawable.stat_notify_more); + builder.setWhen(System.currentTimeMillis()); + builder.setContentTitle("Builder GPS Tracking"); + builder.setContentText("Tracking stopped"); + builder.setContentIntent(intentBack); + builder.setAutoCancel(true); + Notification notify = builder.build(); + + notifier.notify(GPS_NOTIFY, notify); + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + // we only have one, so no need to check the intent + return mRemoteInterfaceBinder; + } + + // our remote interface + private final IRemoteInterface.Stub mRemoteInterfaceBinder = new IRemoteInterface.Stub() { + public Location getLastLocation() { + Log.v("interface", "getLastLocation() called"); + return lastLocation; + } + + public GPXPoint getGPXPoint() { + if (lastLocation == null) { + return null; + } else { + Log.v("interface", "getGPXPoint() called"); + GPXPoint point = new GPXPoint(); + point.elevation = lastLocation.getAltitude(); + point.latitude = (int) (lastLocation.getLatitude() * 1E6); + point.longitude = (int) (lastLocation.getLongitude() * 1E6); + point.timestamp = new Date(lastLocation.getTime()); + return point; + } + } + }; + + private Location firstLocation = null; + private Location lastLocation = null; + private long lastTime = -1; + private long firstTime = -1; + + private LocationListener trackListener = new LocationListener() { + public void onLocationChanged(Location location) { + long thisTime = System.currentTimeMillis(); + long diffTime = thisTime - lastTime; + Log.v(DEBUG_TAG, "diffTime == " + diffTime + ", updateRate = " + + updateRate); + if (diffTime < updateRate) { + // it hasn't been long enough yet + return; + } + lastTime = thisTime; + String locInfo = String.format( + Locale.getDefault(), + "Current loc = (%f, %f) @ (%.1f meters up)", + location.getLatitude(), location.getLongitude(), + location.getAltitude()); + if (lastLocation != null) { + float distance = location.distanceTo(lastLocation); + locInfo += String.format("\n Distance from last = %.1f meters", + distance); + float lastSpeed = distance / diffTime; + locInfo += String.format("\n\tSpeed: %.1fm/s", lastSpeed); + if (location.hasSpeed()) { + float gpsSpeed = location.getSpeed(); + locInfo += String.format(" (or %.1fm/s)", lastSpeed, + gpsSpeed); + } else { + } + } + if (firstLocation != null && firstTime != -1) { + float overallDistance = location.distanceTo(firstLocation); + float overallSpeed = overallDistance / (thisTime - firstTime); + locInfo += String.format( + "\n\tOverall speed: %.1fm/s over %.1f meters", + overallSpeed, overallDistance); + } + lastLocation = location; + if (firstLocation == null) { + firstLocation = location; + firstTime = thisTime; + } + Toast.makeText(getApplicationContext(), locInfo, Toast.LENGTH_LONG) + .show(); + Log.v(DEBUG_TAG, "Test time"); + } + + public void onProviderDisabled(String provider) { + Log.v(DEBUG_TAG, "Provider disabled " + provider); + } + + public void onProviderEnabled(String provider) { + Log.v(DEBUG_TAG, "Provider enabled " + provider); + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; +} diff --git a/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/MenuActivity.java b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/MenuActivity.java new file mode 100644 index 0000000..a904c02 --- /dev/null +++ b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/MenuActivity.java @@ -0,0 +1,43 @@ +package com.advancedandroidbook.simpleservice; + +import android.app.ListActivity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import java.util.SortedMap; +import java.util.TreeMap; + +public abstract class MenuActivity extends ListActivity { + private SortedMap actions = new TreeMap(); + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + String key = (String) l.getItemAtPosition(position); + startActivity((Intent) actions.get(key)); + } + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + prepareMenu(); + + String[] keys = actions.keySet().toArray( + new String[actions.keySet().size()]); + + setListAdapter(new ArrayAdapter(this, + android.R.layout.simple_list_item_1, keys)); + } + + public void addMenuItem(String label, Class cls) { + actions.put(label, new Intent(this, cls)); + } + + abstract void prepareMenu(); +} diff --git a/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/ServiceControlActivity.java b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/ServiceControlActivity.java new file mode 100644 index 0000000..1913afe --- /dev/null +++ b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/ServiceControlActivity.java @@ -0,0 +1,118 @@ +package com.advancedandroidbook.simpleservice; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.Location; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +public class ServiceControlActivity extends Activity implements + ServiceConnection { + IRemoteInterface mRemoteInterface = null; + + // Flag to keep track of bound status + private boolean mIsBound; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.control); + + final TextView status = (TextView) findViewById(R.id.status); + + Button go = (Button) findViewById(R.id.go); + go.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Intent service = new Intent(GPXService.GPX_SERVICE); + service.setPackage("com.advancedandroidbook.simpleservice"); + service.putExtra(GPXService.EXTRA_UPDATE_RATE, 5000); + startService(service); + } + }); + + Button stop = (Button) findViewById(R.id.stop); + stop.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + releaseBind(); + Intent intent = new Intent(GPXService.GPX_SERVICE); + intent.setPackage("com.advancedandroidbook.simpleservice"); + stopService(intent); + } + }); + + Button getLastLoc = (Button) findViewById(R.id.get_last); + getLastLoc.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + try { + String info = "Info from remote: \n"; + Location loc = mRemoteInterface.getLastLocation(); + if (loc != null) { + double lat = loc.getLatitude(); + double lon = loc.getLongitude(); + info += String.format("Last location = (%f, %f)\n", + lat, lon); + } else { + info += "No last location yet.\n"; + } + GPXPoint point = mRemoteInterface.getGPXPoint(); + if (point != null) { + info += String + .format("GPX point = (%d, %d) @ (%.1f meters) @ (%s)\n", + point.latitude, point.longitude, + point.elevation, + point.timestamp.toString()); + } + status.setText(info); + } catch (RemoteException e) { + Log.e("ServiceControl", "Call to remote interface failed.", e); + } + } + }); + } + + public void onServiceConnected(ComponentName name, IBinder service) { + mRemoteInterface = IRemoteInterface.Stub.asInterface(service); + Log.v("ServiceControl", "Interface bound."); + Button getLastLoc = (Button) findViewById(R.id.get_last); + getLastLoc.setVisibility(View.VISIBLE); + } + + public void onServiceDisconnected(ComponentName name) { + mRemoteInterface = null; + Button getLastLoc = (Button) findViewById(R.id.get_last); + getLastLoc.setVisibility(View.GONE); + Log.v("ServiceControl", "Remote interface no longer bound"); + } + + public void releaseBind() { + if (mIsBound) { + unbindService(this); + mIsBound = false; + } + } + + @Override + protected void onResume() { + super.onResume(); + // get a link to our remote service + Intent intent = new Intent(IRemoteInterface.class.getName()); + intent.setPackage("com.advancedandroidbook.simpleservice"); + bindService(intent, this, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + @Override + protected void onPause() { + // remove the link to the remote service +// releaseBind(); + super.onPause(); + } +} \ No newline at end of file diff --git a/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/SimpleServiceActivity.java b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/SimpleServiceActivity.java new file mode 100644 index 0000000..da6923d --- /dev/null +++ b/Chapter 02 - Services/UseService/app/src/main/java/com/advancedandroidbook/simpleservice/SimpleServiceActivity.java @@ -0,0 +1,8 @@ +package com.advancedandroidbook.simpleservice; + +public class SimpleServiceActivity extends MenuActivity { + @Override + void prepareMenu() { + addMenuItem("1. Service Control", ServiceControlActivity.class); + } +} \ No newline at end of file diff --git a/Chapter 16 - Hardware APIs/SimpleBluetooth/res/drawable-hdpi/icon.png b/Chapter 02 - Services/UseService/app/src/main/res/drawable-hdpi/icon.png similarity index 100% rename from Chapter 16 - Hardware APIs/SimpleBluetooth/res/drawable-hdpi/icon.png rename to Chapter 02 - Services/UseService/app/src/main/res/drawable-hdpi/icon.png diff --git a/Chapter 16 - Hardware APIs/SimpleBluetooth/res/drawable-ldpi/icon.png b/Chapter 02 - Services/UseService/app/src/main/res/drawable-ldpi/icon.png similarity index 100% rename from Chapter 16 - Hardware APIs/SimpleBluetooth/res/drawable-ldpi/icon.png rename to Chapter 02 - Services/UseService/app/src/main/res/drawable-ldpi/icon.png diff --git a/Chapter 16 - Hardware APIs/SimpleBluetooth/res/drawable-mdpi/icon.png b/Chapter 02 - Services/UseService/app/src/main/res/drawable-mdpi/icon.png similarity index 100% rename from Chapter 16 - Hardware APIs/SimpleBluetooth/res/drawable-mdpi/icon.png rename to Chapter 02 - Services/UseService/app/src/main/res/drawable-mdpi/icon.png diff --git a/Chapter 02 - Services/UseService/res/layout/main.xml b/Chapter 02 - Services/UseService/app/src/main/res/layout/control.xml similarity index 90% rename from Chapter 02 - Services/UseService/res/layout/main.xml rename to Chapter 02 - Services/UseService/app/src/main/res/layout/control.xml index 2c04849..c532632 100644 --- a/Chapter 02 - Services/UseService/res/layout/main.xml +++ b/Chapter 02 - Services/UseService/app/src/main/res/layout/control.xml @@ -2,12 +2,12 @@ + android:orientation="vertical"> + android:text="@string/local_use_of_remote_service" /> - - - \ No newline at end of file diff --git a/Chapter 16 - Hardware APIs/SimpleBluetooth/res/raw/ping.mp3 b/Chapter 16 - Hardware APIs/SimpleBluetooth/res/raw/ping.mp3 deleted file mode 100644 index 425c737..0000000 Binary files a/Chapter 16 - Hardware APIs/SimpleBluetooth/res/raw/ping.mp3 and /dev/null differ diff --git a/Chapter 16 - Hardware APIs/SimpleBluetooth/res/values/strings.xml b/Chapter 16 - Hardware APIs/SimpleBluetooth/res/values/strings.xml deleted file mode 100644 index c64e38c..0000000 --- a/Chapter 16 - Hardware APIs/SimpleBluetooth/res/values/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Simple Bluetooth Fun - Simple Bluetooth -Ping! -Marco! -Connect! -Status will show here. -Toggle BT -Cancel -Pick a remote device: - diff --git a/Chapter 16 - Hardware APIs/SimpleBluetooth/src/com/advancedandroidbook/simplebluetooth/SimpleBluetooth.java b/Chapter 16 - Hardware APIs/SimpleBluetooth/src/com/advancedandroidbook/simplebluetooth/SimpleBluetooth.java deleted file mode 100644 index f13b664..0000000 --- a/Chapter 16 - Hardware APIs/SimpleBluetooth/src/com/advancedandroidbook/simplebluetooth/SimpleBluetooth.java +++ /dev/null @@ -1,591 +0,0 @@ -package com.advancedandroidbook.simplebluetooth; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Set; -import java.util.UUID; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothServerSocket; -import android.bluetooth.BluetoothSocket; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.media.MediaPlayer; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.ToggleButton; -import android.widget.AdapterView.OnItemClickListener; - -public class SimpleBluetooth extends Activity { - private static final String SIMPLE_BT_RESPONSE_POLO = "polo"; - private static final String SIMPLE_BT_COMMAND_MARCO = "marco"; - private static final String SIMPLE_BT_COMMAND_PING = "ping"; - private static final int BT_DISCOVERABLE_DURATION = 300; // max duration, - // useful for - // debuggin - // //120; - private static final String DEBUG_TAG = "SimpleBluetooth"; - // generated randomly online; search for "web uuid generate"; and then - // edited slightly, keep in mind it's hex - private static final UUID SIMPLE_BT_APP_UUID = UUID - .fromString("0dfb786a-cafe-feed-cafe-982fdfe4bcbf"); - private static final String SIMPLE_BT_NAME = "SimpleBT"; - // dialog id - private static final int DEVICE_PICKER_DIALOG = 1001; - // intercom with threads - private final Handler handler = new Handler(); - private BluetoothAdapter btAdapter; - private BtReceiver btReceiver; - private ServerListenThread serverListenThread; - private ClientConnectThread clientConnectThread; - private BluetoothDataCommThread bluetoothDataCommThread; - private BluetoothDevice remoteDevice; - private BluetoothSocket activeBluetoothSocket; - - // for sound - private MediaPlayer player; - private HashMap discoveredDevices = new HashMap(); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - btAdapter = BluetoothAdapter.getDefaultAdapter(); - if (btAdapter == null) { - // no bluetooth available on device - setStatus("No bluetooth available. :("); - disableAllButtons(); - } else { - setStatus("Bluetooth available! :)"); - // we need a broadcast receiver now - btReceiver = new BtReceiver(); - // register for state change broadcast events - IntentFilter stateChangedFilter = new IntentFilter( - BluetoothAdapter.ACTION_STATE_CHANGED); - this.registerReceiver(btReceiver, stateChangedFilter); - // register for discovery events - IntentFilter actionFoundFilter = new IntentFilter( - BluetoothDevice.ACTION_FOUND); - registerReceiver(btReceiver, actionFoundFilter); - // check current state - int currentState = btAdapter.getState(); - setUIForBTState(currentState); - if (currentState == BluetoothAdapter.STATE_ON) { - findDevices(); - } - } - } - - private void findDevices() { - String lastUsedRemoteDevice = getLastUsedRemoteBTDevice(); - if (lastUsedRemoteDevice != null) { - setStatus("Checking for known paired devices, namely: " - + lastUsedRemoteDevice); - // see if this device is in a list of currently visible (?), paired - // devices - Set pairedDevices = btAdapter.getBondedDevices(); - for (BluetoothDevice pairedDevice : pairedDevices) { - if (pairedDevice.getAddress().equals(lastUsedRemoteDevice)) { - setStatus("Found device: " + pairedDevice.getName() + "@" - + lastUsedRemoteDevice); - remoteDevice = pairedDevice; - } - } - } - - if (remoteDevice == null) { - setStatus("Starting discovery..."); - // start discovery - if (btAdapter.startDiscovery()) { - setStatus("Discovery started..."); - } - - } - - // also set discoverable - setStatus("Enabling discoverable, user will see dialog..."); - Intent discoverMe = new Intent( - BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); - discoverMe.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, - BT_DISCOVERABLE_DURATION); - startActivity(discoverMe); - - // also start listening for connections - setStatus("Enabling listening socket thread"); - serverListenThread = new ServerListenThread(); - serverListenThread.start(); - } - - private String getLastUsedRemoteBTDevice() { - SharedPreferences prefs = getPreferences(MODE_PRIVATE); - String result = prefs.getString("LAST_REMOTE_DEVICE_ADDRESS", null); - return result; - } - - private void setLastUsedRemoteBTDevice(String name) { - SharedPreferences prefs = getPreferences(MODE_PRIVATE); - Editor edit = prefs.edit(); - edit.putString("LAST_REMOTE_DEVICE_ADDRESS", name); - edit.commit(); - } - - private void disableAllButtons() { - Button button; - int[] buttonIds = { R.id.bt_toggle, R.id.connect, R.id.marco, R.id.ping }; - for (int buttonId : buttonIds) { - button = (Button) findViewById(buttonId); - button.setEnabled(false); - } - } - - private ToggleButton btToggle; - - private void setUIForBTState(int state) { - if (btToggle == null) { - btToggle = (ToggleButton) findViewById(R.id.bt_toggle); - } - switch (state) { - case BluetoothAdapter.STATE_ON: - btToggle.setChecked(true); - btToggle.setEnabled(true); - setStatus("BT state now on"); - break; - case BluetoothAdapter.STATE_OFF: - btToggle.setChecked(false); - btToggle.setEnabled(true); - setStatus("BT state now off"); - break; - case BluetoothAdapter.STATE_TURNING_OFF: - btToggle.setChecked(true); - btToggle.setEnabled(false); - setStatus("BT state turning off"); - break; - case BluetoothAdapter.STATE_TURNING_ON: - btToggle.setChecked(false); - btToggle.setEnabled(false); - setStatus("BT state turning on"); - break; - } - } - - private TextView statusField; - - private void setStatus(String string) { - if (statusField == null) { - statusField = (TextView) findViewById(R.id.output_display); - } - String current = (String) statusField.getText(); - current = string + "\n" + current; - // don't let it get too long - if (current.length() > 1500) { - int truncPoint = current.lastIndexOf("\n"); - current = (String) current.subSequence(0, truncPoint); - } - statusField.setText(current); - } - - public void doToggleBT(View view) { - Log.v(DEBUG_TAG, "doToggleBT() called"); - if (btToggle == null) { - btToggle = (ToggleButton) findViewById(R.id.bt_toggle); - } - // check the new state of the button to see what the user wants done - if (btToggle.isChecked() == false) { - if (serverListenThread != null) { - serverListenThread.stopListening(); - } - if (clientConnectThread != null) { - clientConnectThread.stopConnecting(); - } - if (bluetoothDataCommThread != null) { - bluetoothDataCommThread.disconnect(); - } - btAdapter.cancelDiscovery(); - if (!btAdapter.disable()) { - setStatus("Disable adapter failed"); - } - - remoteDevice = null; - activeBluetoothSocket = null; - serverListenThread = null; - clientConnectThread = null; - bluetoothDataCommThread = null; - discoveredDevices.clear(); - } else { - if (!btAdapter.enable()) { - setStatus("Enable adapter failed"); - } - } - } - - @SuppressWarnings("deprecation") - public void doConnectBT(View view) { - Log.v(DEBUG_TAG, "doConnectBT() called"); - if (remoteDevice != null) { - // connect to remoteDevice - doConnectToDevice(remoteDevice); - } else { - // get the device the user wants to connect to - showDialog(DEVICE_PICKER_DIALOG); - } - } - - public void doConnectToDevice(BluetoothDevice device) { - // halt discovery - btAdapter.cancelDiscovery(); - setStatus("Starting connect thread"); - clientConnectThread = new ClientConnectThread(device); - clientConnectThread.start(); - } - - public void doStartDataCommThread() { - if (activeBluetoothSocket == null) { - setStatus("Can't start datacomm"); - Log.w(DEBUG_TAG, - "Something is wrong, shouldn't be trying to use datacomm when no socket"); - } else { - setStatus("Data comm thread starting"); - bluetoothDataCommThread = new BluetoothDataCommThread( - activeBluetoothSocket); - bluetoothDataCommThread.start(); - } - } - - public void doSendPing(View view) { - Log.v(DEBUG_TAG, "doSendPing() called"); - if (bluetoothDataCommThread != null) { - bluetoothDataCommThread.send(SIMPLE_BT_COMMAND_PING); - } - } - - public void doSendMarco(View view) { - Log.v(DEBUG_TAG, "doSendMarco() called"); - if (bluetoothDataCommThread != null) { - bluetoothDataCommThread.send(SIMPLE_BT_COMMAND_MARCO); - } - } - - public void doHandleReceivedCommand(String rawCommand) { - String command = rawCommand.trim(); - setStatus("Got: " + command); - if (command.equals(SIMPLE_BT_COMMAND_PING)) { - if (player != null) { - player.release(); - } - player = new MediaPlayer(); - try { - player.setDataSource( - this, - Uri.parse("android.resource://com.androidbook.simplebluetooth/" - + R.raw.ping)); - player.prepare(); - player.start(); - bluetoothDataCommThread.send("pong"); - } catch (Exception e) { - Log.e(DEBUG_TAG, "Failed to start audio", e); - setStatus("Got ping, can't play sound"); - bluetoothDataCommThread.send("failed"); - } - - } else if (command.equals(SIMPLE_BT_COMMAND_MARCO)) { - bluetoothDataCommThread.send(SIMPLE_BT_RESPONSE_POLO); - } - } - - private class BtReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - setStatus("Broadcast: Got ACTION_STATE_CHANGED"); - int currentState = intent.getIntExtra( - BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.STATE_OFF); - setUIForBTState(currentState); - if (currentState == BluetoothAdapter.STATE_ON) { - findDevices(); - } - } else if (action.equals(BluetoothDevice.ACTION_FOUND)) { - setStatus("Broadcast: Got ACTION_FOUND"); - BluetoothDevice foundDevice = intent - .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - setStatus("Device: " + foundDevice.getName() + "@" - + foundDevice.getAddress()); - discoveredDevices.put(foundDevice.getName(), foundDevice); - } - } - } - - @Override - protected void onDestroy() { - if (serverListenThread != null) { - serverListenThread.stopListening(); - } - if (clientConnectThread != null) { - clientConnectThread.stopConnecting(); - } - if (bluetoothDataCommThread != null) { - bluetoothDataCommThread.disconnect(); - } - if (activeBluetoothSocket != null) { - try { - activeBluetoothSocket.close(); - } catch (IOException e) { - Log.e(DEBUG_TAG, "Failed to close socket", e); - } - } - btAdapter.cancelDiscovery(); - this.unregisterReceiver(btReceiver); - if (player != null) { - player.stop(); - player.release(); - player = null; - } - super.onDestroy(); - } - - @Override - protected Dialog onCreateDialog(int id) { - Dialog dialog = null; - switch (id) { - case DEVICE_PICKER_DIALOG: - if (discoveredDevices.size() > 0) { - ListView list = new ListView(this); - String[] deviceNames = discoveredDevices.keySet().toArray( - new String[discoveredDevices.keySet().size()]); - ArrayAdapter deviceAdapter = new ArrayAdapter( - this, android.R.layout.simple_list_item_1, deviceNames); - list.setAdapter(deviceAdapter); - list.setOnItemClickListener(new OnItemClickListener() { - @SuppressWarnings("deprecation") - @Override - public void onItemClick(AdapterView adapter, View view, - int position, long id) { - String name = (String) ((TextView) view).getText(); - removeDialog(DEVICE_PICKER_DIALOG); - setStatus("Remote device chosen: " + name); - doConnectToDevice(discoveredDevices.get(name)); - } - }); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setView(list); - builder.setTitle(R.string.pick_device); - builder.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @SuppressWarnings("deprecation") - @Override - public void onClick(DialogInterface dialog, - int which) { - removeDialog(DEVICE_PICKER_DIALOG); - setStatus("No remote BT picked."); - } - }); - dialog = builder.create(); - } else { - setStatus("No devices found to pick from"); - } - break; - } - return dialog; - } - - // helper threads - // server thread; listen for connections. both sides do this. always have to - // have one server and one client - // if a client connects, we get a connected BluetoothSocket to use - // if this device conncets to another server, we'll cancel the listening and - // use that connection, also a BluetoothSocket - private class ServerListenThread extends Thread { - private final BluetoothServerSocket btServerSocket; - - public ServerListenThread() { - BluetoothServerSocket btServerSocket = null; - try { - btServerSocket = btAdapter.listenUsingRfcommWithServiceRecord( - SIMPLE_BT_NAME, SIMPLE_BT_APP_UUID); - } catch (IOException e) { - Log.e(DEBUG_TAG, "Failed to start listening", e); - } - // finalize - this.btServerSocket = btServerSocket; - } - - public void run() { - BluetoothSocket socket = null; - try { - while (true) { - handler.post(new Runnable() { - public void run() { - setStatus("ServerThread: calling accept"); - } - }); - socket = btServerSocket.accept(); - if (socket != null) { - activeBluetoothSocket = socket; - // Do work to manage the connection (in a separate - // thread) - handler.post(new Runnable() { - public void run() { - setStatus("Got a device socket"); - doStartDataCommThread(); - } - }); - btServerSocket.close(); - break; - } - } - } catch (Exception e) { - handler.post(new Runnable() { - public void run() { - setStatus("Listening socket done - failed or cancelled"); - } - }); - } - } - - public void stopListening() { - try { - btServerSocket.close(); - } catch (Exception e) { - Log.e(DEBUG_TAG, "Failed to close listening socket", e); - } - } - } - - // client thread: used to make a synchronous connect call to a device - private class ClientConnectThread extends Thread { - private final BluetoothDevice remoteDevice; - private final BluetoothSocket clientSocket; - - public ClientConnectThread(BluetoothDevice remoteDevice) { - this.remoteDevice = remoteDevice; - BluetoothSocket clientSocket = null; - try { - clientSocket = remoteDevice - .createRfcommSocketToServiceRecord(SIMPLE_BT_APP_UUID); - } catch (IOException e) { - Log.e(DEBUG_TAG, "Failed to open local client socket"); - } - // finalize - this.clientSocket = clientSocket; - } - - public void run() { - boolean success = false; - try { - clientSocket.connect(); - success = true; - } catch (IOException e) { - Log.e(DEBUG_TAG, "Client connect failed or cancelled"); - try { - clientSocket.close(); - } catch (IOException e1) { - Log.e(DEBUG_TAG, "Failed to close socket on error", e); - } - } - final String status; - if (success) { - status = "Connected to remote device"; - activeBluetoothSocket = clientSocket; - // we don't need to keep listening - serverListenThread.stopListening(); - } else { - status = "Failed to connect to remote device"; - activeBluetoothSocket = null; - } - handler.post(new Runnable() { - public void run() { - setStatus(status); - setLastUsedRemoteBTDevice(remoteDevice.getAddress()); - doStartDataCommThread(); - } - }); - } - - public void stopConnecting() { - try { - clientSocket.close(); - } catch (Exception e) { - Log.e(DEBUG_TAG, "Failed to stop connecting", e); - } - } - } - - private class BluetoothDataCommThread extends Thread { - private final BluetoothSocket dataSocket; - private final OutputStream outData; - private final InputStream inData; - - public BluetoothDataCommThread(BluetoothSocket dataSocket) { - this.dataSocket = dataSocket; - OutputStream outData = null; - InputStream inData = null; - try { - outData = dataSocket.getOutputStream(); - inData = dataSocket.getInputStream(); - } catch (IOException e) { - Log.e(DEBUG_TAG, "Failed to get iostream", e); - } - this.inData = inData; - this.outData = outData; - } - - public void run() { - byte[] readBuffer = new byte[64]; - int readSize = 0; - try { - while (true) { - readSize = inData.read(readBuffer); - - final String inStr = new String(readBuffer, 0, readSize); - handler.post(new Runnable() { - public void run() { - doHandleReceivedCommand(inStr); - } - }); - } - } catch (Exception e) { - Log.e(DEBUG_TAG, "Socket failure or closed", e); - } - } - - public boolean send(String out) { - boolean success = false; - try { - outData.write(out.getBytes(), 0, out.length()); - success = true; - } catch (IOException e) { - Log.e(DEBUG_TAG, "Failed to write to remote device", e); - setStatus("Send failed"); - } - return success; - } - - public void disconnect() { - try { - dataSocket.close(); - } catch (Exception e) { - Log.e(DEBUG_TAG, "Failed to close datacomm socket", e); - } - } - } -} \ No newline at end of file