Bad notification for startForeground: invalid channel for service notification - java

This question is for AndroidX not for Android 8. I am trying to use the Google Transport Tracker Demo app.
As the project was on the lower version I migrated it to the AndroidX. I am getting issues on the deprecated Google Api client. I already tried other Stack Overflow questions but they didn't quite work for me.
When I am installing the application on my device it's getting crashed with this error:
Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification
mGoogleApiClient = new GoogleApiClient.Builder is showing as Depricated
build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
buildToolsVersion "28"
defaultConfig {
applicationId "com.google.transporttracker"
minSdkVersion 23
targetSdkVersion 28
versionCode 1
versionName "1.0"
resValue "string", "build_transport_id", (project.findProperty("build_transport_id") ?: "")
resValue "string", "build_email", (project.findProperty("build_email") ?: "")
resValue "string", "build_password", (project.findProperty("build_password") ?: "")
}
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE-FIREBASE.txt'
exclude 'META-INF/NOTICE'
}
}
ext {
support = '29'
playServices = '10.2.4'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.gms:play-services-maps:17.0.0'
testImplementation 'junit:junit:4.12'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation "com.google.android.gms:play-services-gcm:17.0.0"
implementation "com.google.android.gms:play-services-location:17.0.0"
implementation "com.google.firebase:firebase-auth:19.3.2"
implementation "com.google.firebase:firebase-config:19.2.0"
implementation "com.google.firebase:firebase-database:19.3.1"
implementation 'com.android.support:design:28.0.0-alpha3'
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation "com.google.android.gms:play-services-base:17.0.0"
}
apply plugin: 'com.google.gms.google-services'
Trackerservice.java
package com.google.transporttracker;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.gcm.GcmNetworkManager;
import com.google.android.gms.gcm.OneoffTask;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.PowerManager;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileWriter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.LinkedList;
public class TrackerService extends Service implements LocationListener {
private static final String TAG = TrackerService.class.getSimpleName();
public static final String STATUS_INTENT = "status";
private static final int NOTIFICATION_ID = 1;
private static final int FOREGROUND_SERVICE_ID = 1;
private static final int CONFIG_CACHE_EXPIRY = 600; // 10 minutes.
private GoogleApiClient mGoogleApiClient;
private DatabaseReference mFirebaseTransportRef;
private FirebaseRemoteConfig mFirebaseRemoteConfig;
private LinkedList<Map<String, Object>> mTransportStatuses = new LinkedList<>();
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mNotificationBuilder;
private PowerManager.WakeLock mWakelock;
private SharedPreferences mPrefs;
public TrackerService() {
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
buildNotification();
else
startForeground(1, new Notification());
setStatusMessage(R.string.connecting);
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
mFirebaseRemoteConfig.setConfigSettings(configSettings);
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);
mPrefs = getSharedPreferences(getString(R.string.prefs), MODE_PRIVATE);
String email = mPrefs.getString(getString(R.string.email), "");
String password = mPrefs.getString(getString(R.string.password), "");
authenticate(email, password);
}
#Override
public void onDestroy() {
// Set activity title to not tracking.
setStatusMessage(R.string.not_tracking);
// Stop the persistent notification.
mNotificationManager.cancel(NOTIFICATION_ID);
// Stop receiving location updates.
if (mGoogleApiClient != null) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient,
TrackerService.this);
}
// Release the wakelock
if (mWakelock != null) {
mWakelock.release();
}
super.onDestroy();
}
private void authenticate(String email, String password) {
final FirebaseAuth mAuth = FirebaseAuth.getInstance();
mAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(new OnCompleteListener<AuthResult>(){
#Override
public void onComplete(Task<AuthResult> task) {
Log.i(TAG, "authenticate: " + task.isSuccessful());
if (task.isSuccessful()) {
fetchRemoteConfig();
loadPreviousStatuses();
} else {
Toast.makeText(TrackerService.this, R.string.auth_failed,
Toast.LENGTH_SHORT).show();
stopSelf();
}
}
});
}
private void fetchRemoteConfig() {
long cacheExpiration = CONFIG_CACHE_EXPIRY;
if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
cacheExpiration = 0;
}
mFirebaseRemoteConfig.fetch(cacheExpiration)
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.i(TAG, "Remote config fetched");
mFirebaseRemoteConfig.activateFetched();
}
});
}
/**
* Loads previously stored statuses from Firebase, and once retrieved,
* start location tracking.
*/
private void loadPreviousStatuses() {
String transportId = mPrefs.getString(getString(R.string.transport_id), "");
FirebaseAnalytics.getInstance(this).setUserProperty("transportID", transportId);
String path = getString(R.string.firebase_path) + transportId;
mFirebaseTransportRef = FirebaseDatabase.getInstance().getReference(path);
mFirebaseTransportRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot != null) {
for (DataSnapshot transportStatus : snapshot.getChildren()) {
mTransportStatuses.add(Integer.parseInt(transportStatus.getKey()),
(Map<String, Object>) transportStatus.getValue());
}
}
startLocationTracking();
}
#Override
public void onCancelled(DatabaseError error) {
// TODO: Handle gracefully
}
});
}
private GoogleApiClient.ConnectionCallbacks mLocationRequestCallback = new GoogleApiClient
.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
LocationRequest request = new LocationRequest();
request.setInterval(mFirebaseRemoteConfig.getLong("LOCATION_REQUEST_INTERVAL"));
request.setFastestInterval(mFirebaseRemoteConfig.getLong
("LOCATION_REQUEST_INTERVAL_FASTEST"));
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
request, TrackerService.this);
setStatusMessage(R.string.tracking);
// Hold a partial wake lock to keep CPU awake when the we're tracking location.
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
mWakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");
mWakelock.acquire();
}
#Override
public void onConnectionSuspended(int reason) {
// TODO: Handle gracefully
}
};
/**
* Starts location tracking by creating a Google API client, and
* requesting location updates.
*/
private void startLocationTracking() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(mLocationRequestCallback)
.addApi(LocationServices.API)
.build();
startForegroundService()
mGoogleApiClient.connect();
}
/**
* Determines if the current location is approximately the same as the location
* for a particular status. Used to check if we'll add a new status, or
* update the most recent status of we're stationary.
*/
private boolean locationIsAtStatus(Location location, int statusIndex) {
if (mTransportStatuses.size() <= statusIndex) {
return false;
}
Map<String, Object> status = mTransportStatuses.get(statusIndex);
Location locationForStatus = new Location("");
locationForStatus.setLatitude((double) status.get("lat"));
locationForStatus.setLongitude((double) status.get("lng"));
float distance = location.distanceTo(locationForStatus);
Log.d(TAG, String.format("Distance from status %s is %sm", statusIndex, distance));
return distance < mFirebaseRemoteConfig.getLong("LOCATION_MIN_DISTANCE_CHANGED");
}
private float getBatteryLevel() {
Intent batteryStatus = registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int batteryLevel = -1;
int batteryScale = 1;
if (batteryStatus != null) {
batteryLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, batteryLevel);
batteryScale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, batteryScale);
}
return batteryLevel / (float) batteryScale * 100;
}
private void logStatusToStorage(Map<String, Object> transportStatus) {
try {
File path = new File(Environment.getExternalStoragePublicDirectory(""),
"transport-tracker-log.txt");
if (!path.exists()) {
path.createNewFile();
}
FileWriter logFile = new FileWriter(path.getAbsolutePath(), true);
logFile.append(transportStatus.toString() + "\n");
logFile.close();
} catch (Exception e) {
Log.e(TAG, "Log file error", e);
}
}
private void shutdownAndScheduleStartup(int when) {
Log.i(TAG, "overnight shutdown, seconds to startup: " + when);
com.google.android.gms.gcm.Task task = new OneoffTask.Builder()
.setService(TrackerTaskService.class)
.setExecutionWindow(when, when + 60)
.setUpdateCurrent(true)
.setTag(TrackerTaskService.TAG)
.setRequiredNetwork(com.google.android.gms.gcm.Task.NETWORK_STATE_ANY)
.setRequiresCharging(false)
.build();
GcmNetworkManager.getInstance(this).schedule(task);
stopSelf();
}
/**
* Pushes a new status to Firebase when location changes.
*/
#Override
public void onLocationChanged(Location location) {
fetchRemoteConfig();
long hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
int startupSeconds = (int) (mFirebaseRemoteConfig.getDouble("SLEEP_HOURS_DURATION") * 3600);
if (hour == mFirebaseRemoteConfig.getLong("SLEEP_HOUR_OF_DAY")) {
shutdownAndScheduleStartup(startupSeconds);
return;
}
Map<String, Object> transportStatus = new HashMap<>();
transportStatus.put("lat", location.getLatitude());
transportStatus.put("lng", location.getLongitude());
transportStatus.put("time", new Date().getTime());
transportStatus.put("power", getBatteryLevel());
if (locationIsAtStatus(location, 1) && locationIsAtStatus(location, 0)) {
// If the most recent two statuses are approximately at the same
// location as the new current location, rather than adding the new
// location, we update the latest status with the current. Two statuses
// are kept when the locations are the same, the earlier representing
// the time the location was arrived at, and the latest representing the
// current time.
mTransportStatuses.set(0, transportStatus);
// Only need to update 0th status, so we can save bandwidth.
mFirebaseTransportRef.child("0").setValue(transportStatus);
} else {
// Maintain a fixed number of previous statuses.
while (mTransportStatuses.size() >= mFirebaseRemoteConfig.getLong("MAX_STATUSES")) {
mTransportStatuses.removeLast();
}
mTransportStatuses.addFirst(transportStatus);
// We push the entire list at once since each key/index changes, to
// minimize network requests.
mFirebaseTransportRef.setValue(mTransportStatuses);
}
if (BuildConfig.DEBUG) {
logStatusToStorage(transportStatus);
}
NetworkInfo info = ((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE))
.getActiveNetworkInfo();
boolean connected = info != null && info.isConnectedOrConnecting();
setStatusMessage(connected ? R.string.tracking : R.string.not_tracking);
}
private void buildNotification() {
String NOTIFICATION_CHANNEL_ID = "com.google.transporttracker";
String channelName = "My Background Service";
String channelId = "1";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
NotificationChannel channel = new NotificationChannel(channelId, "LOGIPACE", NotificationManager.IMPORTANCE_DEFAULT);
mNotificationManager.createNotificationChannel(channel);
}
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, TrackerActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
mNotificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.bus_white)
.setColor(getColor(R.color.colorPrimary))
.setContentTitle(getString(R.string.app_name))
.setOngoing(true)
.setContentIntent(resultPendingIntent);
startForeground(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
}
/**
* Sets the current status message (connecting/tracking/not tracking).
*/
private void setStatusMessage(int stringId) {
mNotificationBuilder.setContentText(getString(stringId));
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
// Also display the status message in the activity.
Intent intent = new Intent(STATUS_INTENT);
intent.putExtra(getString(R.string.status), stringId);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}

In your buildNotification() function, you are using mNotificationManager to create the channel but the affection of the manager is 3 lines below.
Thus, when you try to create the channel, the manager is null and the channel is never created.
You should put mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); instruction before the SDK version test.

Related

CameraXLivePreview: Can not create image processor: Object Detection

I'm creating an app using a MLkit and finally I made it. But it's not working and keep send me a errors I can't understand why error keep occurs.. here is my code and error pls help me
I just copied from MLKit example quick start sample for only using a pose
detector and it says " Invalid model name" .. I don't know why it does saying can you guys help me??
/*
* Copyright 2020 Google LLC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.pose;
import androidx.camera.core.ImageAnalysis;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.util.Size;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback;
import androidx.core.content.ContextCompat;
import com.google.android.gms.common.annotation.KeepName;
import com.google.mlkit.common.MlKitException;
import com.google.mlkit.common.model.LocalModel;
import com.example.pose.CameraXViewModel;
import com.example.pose.GraphicOverlay;
import com.google.mlkit.vision.demo.R;
import com.example.pose.VisionImageProcessor;
import com.example.pose.posedetector.PoseDetectorProcessor;
import com.example.pose.preference.PreferenceUtils;
import com.google.mlkit.vision.pose.PoseDetectorOptions;
import java.util.ArrayList;
import java.util.List;
/** Live preview demo app for ML Kit APIs using CameraX. */
#KeepName
#RequiresApi(VERSION_CODES.LOLLIPOP)
public final class CameraXLivePreviewActivity extends AppCompatActivity
implements OnRequestPermissionsResultCallback,
OnItemSelectedListener,
CompoundButton.OnCheckedChangeListener {
private static final String TAG = "CameraXLivePreview";
private static final int PERMISSION_REQUESTS = 1;
private static final String OBJECT_DETECTION = "Object Detection";
private static final String POSE_DETECTION = "Pose Detection";
private static final String STATE_SELECTED_MODEL = "selected_model";
private static final String STATE_LENS_FACING = "lens_facing";
private PreviewView previewView;
private GraphicOverlay graphicOverlay;
#Nullable private ProcessCameraProvider cameraProvider;
#Nullable private Preview previewUseCase;
#Nullable private ImageAnalysis analysisUseCase;
#Nullable private VisionImageProcessor imageProcessor;
private boolean needUpdateGraphicOverlayImageSourceInfo;
private String selectedModel = OBJECT_DETECTION;
private int lensFacing = CameraSelector.LENS_FACING_BACK;
private CameraSelector cameraSelector;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
Toast.makeText(
getApplicationContext(),
"CameraX is only supported on SDK version >=21. Current SDK version is "
+ VERSION.SDK_INT,
Toast.LENGTH_LONG)
.show();
return;
}
if (savedInstanceState != null) {
selectedModel = savedInstanceState.getString(STATE_SELECTED_MODEL, OBJECT_DETECTION);
lensFacing = savedInstanceState.getInt(STATE_LENS_FACING, CameraSelector.LENS_FACING_BACK);
}
cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build();
setContentView(R.layout.activity_vision_camerax_live_preview);
previewView = findViewById(R.id.preview_view);
if (previewView == null) {
Log.d(TAG, "previewView is null");
}
graphicOverlay = findViewById(R.id.graphic_overlay);
if (graphicOverlay == null) {
Log.d(TAG, "graphicOverlay is null");
}
ToggleButton facingSwitch = findViewById(R.id.facing_switch);
facingSwitch.setOnCheckedChangeListener(this);
new ViewModelProvider(this, AndroidViewModelFactory.getInstance(getApplication()))
.get(CameraXViewModel.class)
.getProcessCameraProvider()
.observe(
this,
provider -> {
cameraProvider = provider;
if (allPermissionsGranted()) {
bindAllCameraUseCases();
}
});
if (!allPermissionsGranted()) {
getRuntimePermissions();
}
}
#Override
protected void onSaveInstanceState(#NonNull Bundle bundle) {
super.onSaveInstanceState(bundle);
bundle.putString(STATE_SELECTED_MODEL, selectedModel);
bundle.putInt(STATE_LENS_FACING, lensFacing);
}
#Override
public synchronized void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
// An item was selected. You can retrieve the selected item using
// parent.getItemAtPosition(pos)
selectedModel = parent.getItemAtPosition(pos).toString();
Log.d(TAG, "Selected model: " + selectedModel);
bindAnalysisUseCase();
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
// Do nothing.
}
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.d(TAG, "Set facing");
if (cameraProvider == null) {
return;
}
int newLensFacing =
lensFacing == CameraSelector.LENS_FACING_FRONT
? CameraSelector.LENS_FACING_BACK
: CameraSelector.LENS_FACING_FRONT;
CameraSelector newCameraSelector =
new CameraSelector.Builder().requireLensFacing(newLensFacing).build();
try {
if (cameraProvider.hasCamera(newCameraSelector)) {
lensFacing = newLensFacing;
cameraSelector = newCameraSelector;
bindAllCameraUseCases();
return;
}
} catch (CameraInfoUnavailableException e) {
// Falls through
}
Toast.makeText(
getApplicationContext(),
"This device does not have lens with facing: " + newLensFacing,
Toast.LENGTH_SHORT)
.show();
}
#Override
public void onResume() {
super.onResume();
bindAllCameraUseCases();
}
#Override
protected void onPause() {
super.onPause();
if (imageProcessor != null) {
imageProcessor.stop();
}
}
#Override
public void onDestroy() {
super.onDestroy();
if (imageProcessor != null) {
imageProcessor.stop();
}
}
private void bindAllCameraUseCases() {
if (cameraProvider != null) {
// As required by CameraX API, unbinds all use cases before trying to re-bind any of them.
cameraProvider.unbindAll();
bindPreviewUseCase();
bindAnalysisUseCase();
}
}
private void bindPreviewUseCase() {
if (!PreferenceUtils.isCameraLiveViewportEnabled(this)) {
return;
}
if (cameraProvider == null) {
return;
}
if (previewUseCase != null) {
cameraProvider.unbind(previewUseCase);
}
previewUseCase = new Preview.Builder().build();
previewUseCase.setSurfaceProvider(previewView.createSurfaceProvider());
cameraProvider.bindToLifecycle(/* lifecycleOwner= */ this, cameraSelector, previewUseCase);
}
private void bindAnalysisUseCase() {
if (cameraProvider == null) {
return;
}
if (analysisUseCase != null) {
cameraProvider.unbind(analysisUseCase);
}
if (imageProcessor != null) {
imageProcessor.stop();
}
try {
switch (selectedModel) {
case POSE_DETECTION:
PoseDetectorOptions poseDetectorOptions =
PreferenceUtils.getPoseDetectorOptionsForLivePreview(this);
boolean shouldShowInFrameLikelihood =
PreferenceUtils.shouldShowPoseDetectionInFrameLikelihoodLivePreview(this);
imageProcessor =
new PoseDetectorProcessor(this, poseDetectorOptions, shouldShowInFrameLikelihood);
break;
default:
throw new IllegalStateException("Invalid model name");
}
} catch (Exception e) {
Log.e(TAG, "Can not create image processor: " + selectedModel, e);
Toast.makeText(
getApplicationContext(),
"Can not create image processor: " + e.getLocalizedMessage(),
Toast.LENGTH_LONG)
.show();
return;
}
ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
Size targetAnalysisSize = PreferenceUtils.getCameraXTargetAnalysisSize(this);
if (targetAnalysisSize != null) {
builder.setTargetResolution(targetAnalysisSize);
}
analysisUseCase = builder.build();
needUpdateGraphicOverlayImageSourceInfo = true;
analysisUseCase.setAnalyzer(
// imageProcessor.processImageProxy will use another thread to run the detection underneath,
// thus we can just runs the analyzer itself on main thread.
ContextCompat.getMainExecutor(this),
imageProxy -> {
if (needUpdateGraphicOverlayImageSourceInfo) {
boolean isImageFlipped = lensFacing == CameraSelector.LENS_FACING_FRONT;
int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
if (rotationDegrees == 0 || rotationDegrees == 180) {
graphicOverlay.setImageSourceInfo(
imageProxy.getWidth(), imageProxy.getHeight(), isImageFlipped);
} else {
graphicOverlay.setImageSourceInfo(
imageProxy.getHeight(), imageProxy.getWidth(), isImageFlipped);
}
needUpdateGraphicOverlayImageSourceInfo = false;
}
try {
imageProcessor.processImageProxy(imageProxy, graphicOverlay);
} catch (MlKitException e) {
Log.e(TAG, "Failed to process image. Error: " + e.getLocalizedMessage());
Toast.makeText(getApplicationContext(), e.getLocalizedMessage(), Toast.LENGTH_SHORT)
.show();
}
});
cameraProvider.bindToLifecycle(/* lifecycleOwner= */ this, cameraSelector, analysisUseCase);
}
private String[] getRequiredPermissions() {
try {
PackageInfo info =
this.getPackageManager()
.getPackageInfo(this.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] ps = info.requestedPermissions;
if (ps != null && ps.length > 0) {
return ps;
} else {
return new String[0];
}
} catch (Exception e) {
return new String[0];
}
}
private boolean allPermissionsGranted() {
for (String permission : getRequiredPermissions()) {
if (!isPermissionGranted(this, permission)) {
return false;
}
}
return true;
}
private void getRuntimePermissions() {
List<String> allNeededPermissions = new ArrayList<>();
for (String permission : getRequiredPermissions()) {
if (!isPermissionGranted(this, permission)) {
allNeededPermissions.add(permission);
}
}
if (!allNeededPermissions.isEmpty()) {
ActivityCompat.requestPermissions(
this, allNeededPermissions.toArray(new String[0]), PERMISSION_REQUESTS);
}
}
#Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
Log.i(TAG, "Permission granted!");
if (allPermissionsGranted()) {
bindAllCameraUseCases();
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private static boolean isPermissionGranted(Context context, String permission) {
if (ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Permission granted: " + permission);
return true;
}
Log.i(TAG, "Permission NOT granted: " + permission);
return false;
}
}
E/CameraXLivePreview: Can not create image processor: Object Detection
java.lang.IllegalStateException: Invalid model name
at com.example.pose.CameraXLivePreviewActivity.bindAnalysisUseCase(CameraXLivePreviewActivity.java:271)
at com.example.pose.CameraXLivePreviewActivity.bindAllCameraUseCases(CameraXLivePreviewActivity.java:229)
at com.example.pose.CameraXLivePreviewActivity.onCheckedChanged(CameraXLivePreviewActivity.java:187)
at android.widget.CompoundButton.setChecked(CompoundButton.java:218)
at android.widget.ToggleButton.setChecked(ToggleButton.java:81)
at android.widget.CompoundButton.toggle(CompoundButton.java:137)
at android.widget.CompoundButton.performClick(CompoundButton.java:142)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Please change this line to let the feature selector to select Pose detection
"private String selectedModel = OBJECT_DETECTION;"
to
"private String selectedModel = POSE_DETECTION;"

MLKit Object Detection is not detecting objects

MLKit by Google (without Firebase) is new, so I'm having trouble. I'm trying to follow this example here: https://developers.google.com/ml-kit/vision/object-detection/custom-models/android
The app opens fine, & the camera works (As in, I can see things). But the actual detection doesn't seem to work.
Am I missing part of the code to actually detect the object? Or is it an issue with the implementation of CameraX or ImageInput?
package com.example.mlkitobjecttest;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraX;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.core.impl.PreviewConfig;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.media.Image;
import android.os.Bundle;
import android.text.Layout;
import android.util.Rational;
import android.util.Size;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.mlkit.common.model.LocalModel;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.objects.DetectedObject;
import com.google.mlkit.vision.objects.ObjectDetection;
import com.google.mlkit.vision.objects.ObjectDetector;
import com.google.mlkit.vision.objects.custom.CustomObjectDetectorOptions;
import org.w3c.dom.Text;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private class YourAnalyzer implements ImageAnalysis.Analyzer {
#Override
#androidx.camera.core.ExperimentalGetImage
public void analyze(ImageProxy imageProxy) {
Image mediaImage = imageProxy.getImage();
if (mediaImage != null) {
InputImage image =
InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
// Pass image to an ML Kit Vision API
// ...
LocalModel localModel =
new LocalModel.Builder()
.setAssetFilePath("mobilenet_v1_1.0_128_quantized_1_default_1.tflite")
// or .setAbsoluteFilePath(absolute file path to tflite model)
.build();
CustomObjectDetectorOptions customObjectDetectorOptions =
new CustomObjectDetectorOptions.Builder(localModel)
.setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE)
.enableMultipleObjects()
.enableClassification()
.setClassificationConfidenceThreshold(0.5f)
.setMaxPerObjectLabelCount(3)
.build();
ObjectDetector objectDetector =
ObjectDetection.getClient(customObjectDetectorOptions);
objectDetector
.process(image)
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
//Toast.makeText(getApplicationContext(), "Fail. Sad!", Toast.LENGTH_SHORT).show();
//textView.setText("Fail. Sad!");
imageProxy.close();
}
})
.addOnSuccessListener(new OnSuccessListener<List<DetectedObject>>() {
#Override
public void onSuccess(List<DetectedObject> results) {
for (DetectedObject detectedObject : results) {
Rect box = detectedObject.getBoundingBox();
for (DetectedObject.Label label : detectedObject.getLabels()) {
String text = label.getText();
int index = label.getIndex();
float confidence = label.getConfidence();
textView.setText(text);
}}
imageProxy.close();
}
});
}
//ImageAnalysis.Builder.fromConfig(new ImageAnalysisConfig).setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST);
}
}
PreviewView prevView;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private ExecutorService executor = Executors.newSingleThreadExecutor();
TextView textView;
private int REQUEST_CODE_PERMISSIONS = 101;
private String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA"};
/* #NonNull
#Override
public CameraXConfig getCameraXConfig() {
return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setCameraExecutor(ContextCompat.getMainExecutor(this))
.build();
}
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
prevView = findViewById(R.id.viewFinder);
textView = findViewById(R.id.scan_button);
if(allPermissionsGranted()){
startCamera();
}else{
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}
}
private void startCamera() {
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(new Runnable() {
#Override
public void run() {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindPreview(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
// No errors need to be handled for this Future.
// This should never be reached.
}
}
}, ContextCompat.getMainExecutor(this));
}
void bindPreview(#NonNull ProcessCameraProvider cameraProvider) {
Preview preview = new Preview.Builder()
.build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
preview.setSurfaceProvider(prevView.createSurfaceProvider());
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new YourAnalyzer());
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, preview, imageAnalysis);
}
private boolean allPermissionsGranted() {
for(String permission: REQUIRED_PERMISSIONS){
if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
return false;
}
}
return true;
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if(requestCode == REQUEST_CODE_PERMISSIONS){
if(allPermissionsGranted()){
startCamera();
} else{
Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
this.finish();
}
}
}
}
Nothing is detected because you defined the wrong path to tflite model file. You emulator or physical device cannot resolve given path as it doesn't exists on mobile device: C:\\Users\\dude\\Documents\\mlkitobjecttest\\app\\src\\main\\assets\\mobilenet_v1_1.0_128_quantized_1_default_1.tflite
Copy your model mobilenet_v1_1.0_128_quantized_1_default_1.tflite into assets directory under you app's project src/main directory.
If you do not have that directory just create a new one named assets.
At the end it should look like this:
After that fix LocalModel initialization code:
LocalModel localModel =
new LocalModel.Builder()
.setAssetFilePath("mobilenet_v1_1.0_128_quantized_1_default_1.tflite")
// or .setAbsoluteFilePath(absolute file path to tflite model)
.build();
Update: one more issue found
ImageAnalysis instance was not bound to CameraProvider:
...
ImageAnalysis imageAnalysis = ...
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, preview); // imageAnalysis is not used
To fix it just pass as the last argument imageAnalysis variable into bindToLifecycle method:
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, preview, imageAnalysis);
Second update: another one issue found
MLKit cannot process an image because it was closed while it was processing or right before processing started. I'm talking about imageProxy.close() line of code declared inside of public void analyze(ImageProxy imageProxy).
Java documentation of close() method:
/**
* Free up this frame for reuse.
* <p>
* After calling this method, calling any methods on this {#code Image} will
* result in an {#link IllegalStateException}, and attempting to read from
* or write to {#link ByteBuffer ByteBuffers} returned by an earlier
* {#link Plane#getBuffer} call will have undefined behavior. If the image
* was obtained from {#link ImageWriter} via
* {#link ImageWriter#dequeueInputImage()}, after calling this method, any
* image data filled by the application will be lost and the image will be
* returned to {#link ImageWriter} for reuse. Images given to
* {#link ImageWriter#queueInputImage queueInputImage()} are automatically
* closed.
* </p>
*/
To fix that move imageProxy.close() into failure and success listeners:
objectDetector
.process(image)
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(getApplicationContext(), "Fail. Sad!", Toast.LENGTH_LONG).show();
...
imageProxy.close();
}
})
.addOnSuccessListener(new OnSuccessListener<List<DetectedObject>>() {
#Override
public void onSuccess(List<DetectedObject> results) {
Toast.makeText(getBaseContext(), "Success...", Toast.LENGTH_LONG).show();
...
imageProxy.close();
}
});
The fixed solution was tested with image classification model from Tensorflow and test was successful.

Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference error

I am trying to get a notification to trigger when a long is below a certain number. However whenever sendNotification() is called it throws the above error.
I am new to android.
Below is the section of the code where the issue is.
I am not sure what is causing this error. I supect I made need to change the method to sendNotification(View view) but in that case what do I send as the view?
I can provide the full code if needed.
package com.mple.seriestracker;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import com.mple.seriestracker.activity.HomeScreenActivity;
import com.mple.seriestracker.api.episodate.entities.show.Episode;
import com.mple.seriestracker.util.NotificationGenerator;
import org.threeten.bp.Duration;
import org.threeten.bp.LocalDateTime;
import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.format.DateTimeFormatter;
import java.util.Locale;
public class Countdown extends AppCompatActivity{
private int season;
private int episode;
private String name;
private ZonedDateTime airDate;
private Context context;
public Countdown(String name, int episode,int season,ZonedDateTime airDate, Context context){
this.airDate = airDate;
this.name = name;
this.episode = episode;
this.season = season;
this.context = context;
}
public Countdown(com.mple.seriestracker.api.episodate.entities.show.Countdown countdown){
this.airDate = parseToLocal(countdown.air_date);
this.name = countdown.name;
this.episode = countdown.episode;
this.season = countdown.season;
}
public void getSecondsTillAiring(){
Duration duration = Duration.between(LocalDateTime.now(),airDate);
long days = duration.toDays();
//No idea why this returns an absurd number, possibly something wrong with the time conversion
//So the simple fix is to convert the days into hours, subtract the total hours with the days.
//This returns the real value, and makes it accurate.
long hours = duration.toHours()-(days*24);
long minutes = (int) ((duration.getSeconds() % (60 * 60)) / 60);
long seconds = (int) (duration.getSeconds() % 60);
if(days > 0){
hours += days * 24;
}
if(hours > 0){
minutes += 60* hours;
}
if(minutes > 0){
seconds += 60 * minutes;
}
if (seconds < 432000){
sendNotification();
}
}
public void sendNotification()
{
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "M_CH_ID");
//Create the intent that’ll fire when the user taps the notification//
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.androidauthority.com/"));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
notificationBuilder.setContentIntent(pendingIntent);
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel =
new NotificationChannel("M_CH_ID", "M_CH_ID", NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.setDescription("Test");
nm.createNotificationChannel(notificationChannel);
notificationBuilder.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher)
.setTicker("Hearty365")
.setContentTitle("Default notification")
.setContentText("Random words")
.setContentInfo("Info");
nm.notify(1, notificationBuilder.build());
}
public String getCountdownFormat(){
getSecondsTillAiring();
Duration duration = Duration.between(LocalDateTime.now(),airDate);
long days = duration.toDays();
//No idea why this returns an absurd number, possibly something wrong with the time conversion
//So the simple fix is to convert the days into hours, subtract the total hours with the days.
//This returns the real value, and makes it accurate.
long hours = duration.toHours()-(days*24);
int minutes = (int) ((duration.getSeconds() % (60 * 60)) / 60);
int seconds = (int) (duration.getSeconds() % 60);
String timeString = "";
if(days > 0){
timeString+=formatDay(days);
}
if(hours > 0){
timeString+=formatHour(hours);
}
if(minutes > 0){
timeString+= formatMinutes(minutes);
}
if(seconds > 0){
timeString += formatSeconds(seconds);
}
return timeString;
}
public String getName() {
return name;
}
public int getEpisode() {
return episode;
}
public int getSeason() {
return season;
}
private String formatDay(long days){
return format(days,"day");
}
private String formatHour(long hours){
return format(hours,"hour");
}
private String formatMinutes(long minutes){
return format(minutes,"minute");
}
private String formatSeconds(long seconds){
return format(seconds,"second");
}
private String format(long x,String nonPlural){
//Checks whether or not a plural should be added
String string = nonPlural;
if(x > 1)
string+="s";
return String.format("%s %s ",x,string);
}
//All air dates are formatted in this format
static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
public static ZonedDateTime parseToLocal(String s){
if(s == null) return null;
return LocalDateTime.parse(s, DATE_TIME_FORMATTER)
.atOffset(ZoneOffset.UTC)
.atZoneSameInstant(ZoneId.systemDefault());
}
public static boolean isOlderEpisode(LocalDateTime airDate, LocalDateTime currEpDate){
return currEpDate.isBefore(airDate);
}
public static boolean isOlderEpisode(OffsetDateTime airDate, OffsetDateTime currEpDate){
return currEpDate.toLocalDate().isBefore(airDate.toLocalDate());
}
//Responsible for finding a certain episode
public Countdown getUpcomingAiringEp(Episode[] episodes, int episode, int season) {
if (episodes == null) {
return null;
}
//Loop in reverse, since the episodes are ordered from start to finish
//So looping from reverse will start with the newer shows first
for (int i = (episodes.length - 1); i >= 0; i--) {
Episode newEpisode = episodes[i];
if (newEpisode.air_date != null && newEpisode.season == season && newEpisode.episode == episode) {
return new Countdown(newEpisode.name,newEpisode.episode, newEpisode.season, parseToLocal(newEpisode.air_date),this);
}
if(newEpisode.season <= (newEpisode.season - 1)) {
break;
}
}
return null;
}
}
Full stack trace
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mple.seriestracker, PID: 2348
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
at android.content.ContextWrapper.getPackageName(ContextWrapper.java:145)
at android.app.PendingIntent.getActivity(PendingIntent.java:344)
at android.app.PendingIntent.getActivity(PendingIntent.java:311)
at com.mple.seriestracker.Countdown.sendNotification(Countdown.java:76)
at com.mple.seriestracker.Countdown.getSecondsTillAiring(Countdown.java:65)
at com.mple.seriestracker.Countdown.getCountdownFormat(Countdown.java:100)
at com.mple.seriestracker.fragments.CountdownFragment$RecyclerViewAdapter$1.run(CountdownFragment.java:95)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
And the main class
package com.mple.seriestracker.activity;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.jakewharton.threetenabp.AndroidThreeTen;
import com.mple.seriestracker.R;
import com.mple.seriestracker.ShowInfo;
import com.mple.seriestracker.ShowTracker;
import com.mple.seriestracker.TvShow;
import com.mple.seriestracker.api.episodate.Episodate;
import com.mple.seriestracker.api.episodate.entities.show.TvShowResult;
import com.mple.seriestracker.database.EpisodeTrackDatabase;
import com.mple.seriestracker.fragments.CountdownFragment;
import com.mple.seriestracker.fragments.SectionsPagerAdapter;
import com.mple.seriestracker.fragments.MyShowsFragment;
import com.mple.seriestracker.util.NotificationGenerator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Response;
public class HomeScreenActivity extends AppCompatActivity {
static final int NEW_SHOW_REQUEST_CODE = 1;
static final int FILE_PERMISSION_RREQUEST_CODE = 1;
static final int NEW_SHOW_REQUEST_RESULT_CODE = 1;
Context context = this;
SectionsPagerAdapter mSectionsPagerAdapter;
ViewPager mViewPager;
TabLayout mTabs;
boolean started = false;
//TODO allow more than 3 shows to display on countdown page
//TODO sort the countdown tab based on time
//TODO notify the user when a show is airing
//TODO re-obtain the next countdown (if any new episodes) otherwise remove the countdown from the tab
//TODO add delete button to delete shows (holding on image already has checkboxes implemented)
//All done after that
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EpisodeTrackDatabase.setInstance(new EpisodeTrackDatabase(getApplicationContext()));
//Sets all date time stuff to correct sync
AndroidThreeTen.init(this);
//Initialize fragments
mSectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
mViewPager = findViewById(R.id.view_pager);
setupViewPager(mViewPager);
mTabs = findViewById(R.id.tabs);
mTabs.setupWithViewPager(mViewPager);
//Initialize floating menu button
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
startSearchIntent();
}
});
}
#Override
protected void onStart() {
super.onStart();
//On start is called when the search intent is destroyed
//This prevents it from being used more than once.
//As it's intended for loading settings only (after all UI elements are initialized)
if(started) return;
loadSettings();
started = true;
}
//Responsible for setting up the fragments for each tab
private void setupViewPager(ViewPager viewPager){
mSectionsPagerAdapter.addFragment(new MyShowsFragment(),"My Shows");
mSectionsPagerAdapter.addFragment(new CountdownFragment(),"Countdowns");
viewPager.setAdapter(mSectionsPagerAdapter);
}
private void loadSettings(){
//Loads settings from database
new LoadShowsTask().execute();
}
//Adds a show to the "my shows" tab
public void addShow(ShowInfo showInfo){
new TvShowTask().execute(showInfo); //Background task to get info from the api
EpisodeTrackDatabase.INSTANCE.addShow(showInfo.name,showInfo.imagePath,showInfo.id); //Add the show to the database
((MyShowsFragment)mSectionsPagerAdapter.getItem(0)).addShow(showInfo); //Adds it to the fragment, fragment will then automatically update it
}
public void addCountdown(long showID){
if(ShowTracker.INSTANCE.calenderCache.contains(showID))return;
((CountdownFragment)mSectionsPagerAdapter.getItem(1)).addCountdown(showID);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == NEW_SHOW_REQUEST_CODE && resultCode == NEW_SHOW_REQUEST_RESULT_CODE) {
//Create a new show, add it to the list and populate it
ShowInfo showInfo = new ShowInfo(); //Creates a new object
showInfo.id = data.getLongExtra("showID",0);
showInfo.imagePath = data.getStringExtra("showImage");
showInfo.name = data.getStringExtra("showName");
ShowTracker.INSTANCE.addedShowsCache.add(showInfo.id);
addShow(showInfo);
}
}
class LoadShowsTask extends AsyncTask<String,Void,String>{
#Override
protected String doInBackground(String... strings) {
ShowInfo[] showData = EpisodeTrackDatabase.INSTANCE.getAllShows();
runOnUiThread(() ->{
new TvShowTask().execute(showData);
for (ShowInfo show : showData) {
runOnUiThread(() ->addShow(show));
}
});
return null;
}
}
class TvShowTask extends AsyncTask<ShowInfo,Void, List<TvShowResult>> {
//Responsible for obtaining info about each show
//Automatically prompts on each show add/app initialization
#Override
protected List<TvShowResult> doInBackground(ShowInfo ... shows) {
List<TvShowResult> tvShowResults = new ArrayList<>();
for (ShowInfo show: shows) {
try {
Response<com.mple.seriestracker.api.episodate.entities.show.TvShow> response = Episodate.INSTANCE
.show()
.textQuery(show.id + "")
.execute();
if(response.isSuccessful()){
tvShowResults.add(response.body().tvShow);
}
} catch (IOException e) {}
}
return tvShowResults;
}
#Override
protected void onPostExecute(List<TvShowResult> result) {
for (TvShowResult tvShowResult : result) {
TvShow tvShow = new TvShow(tvShowResult);
ShowTracker.INSTANCE.addTvShow(tvShow);
if(tvShow.getCountdown() != null){
addCountdown(tvShow.getId());
}
}
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
switch (requestCode){
case FILE_PERMISSION_RREQUEST_CODE:
if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
//Todo Finish this, if we will be implementing saving
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
}
}
//Prevents the app opening multiple search intents, if spammed
void startSearchIntent(){
if(!ShowSearchActivity.destroyed) return;
Intent intent = new Intent(getApplicationContext(),ShowSearchActivity.class);
startActivityForResult(intent,NEW_SHOW_REQUEST_CODE);
}
//Will be used for writing saved data, later on to keep track of what shows are saved
boolean hasFilePermissions(){
return (Build.VERSION.SDK_INT > 22 && ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED);
}
void askForPermission(){
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, FILE_PERMISSION_RREQUEST_CODE);
}
}
Iam wondering if at this point it is just easiier to link to github as there are more classes than just these 2.
You must supply the proper Context from you Activity class.
First in your YOUR_ACTIVITY.java declare this
Context context = this;
then add context parameters in your countdown method
private int season;
private int episode;
private String name;
private ZonedDateTime airDate;
private Context context;
public Countdown(String name, int episode,int season,ZonedDateTime airDate, Context context){
this.airDate = airDate;
this.name = name;
this.episode = episode;
this.season = season;
this.context = context;
}
Just add the context when calling the Countdown method.
new Countdown(name, episode, season, context).getSecondsTillAiring();
then supply the context to NotificationManager like this.
NotificationManager nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
Just remove "extends AppCompatActivity" might work for you, because apparently your class does not inherit anything from AppCompatActivity.
EDIT:
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
You may consider moving this part of the code to your Activity class,
or passing activity reference to the method and call it like:
NotificationManager nm = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);

cant pass GeoDataClient in placeAutocomplete adapter?

I am trying to make a google place autocomplete adapter. But im getting the following error: Ive been sstuck on this for 2 days now :( so any help would be greatly appreciated!!
cant pass mGeoDataClient
The error i am recieving
Here is my MapClass:
import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.location.places.PlaceDetectionClient;
import com.google.android.gms.location.places.GeoDataClient;
import com.google.android.gms.location.places.Places;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MapActivity extends AppCompatActivity implements OnMapReadyCallback {
protected GeoDataClient mGeoDataClient;
private PlaceDetectionClient mPlaceDetectionClient;
private static final LatLngBounds BOUNDS_GREATER_SYDNEY = new LatLngBounds(
new LatLng(-34.041458, 150.790100), new LatLng(-33.682247, 151.383362));
#Override
public void onMapReady(GoogleMap googleMap) {
Toast.makeText(this, "Map is ready", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onMapReady: map is ready");
mMap = googleMap;
if(mLocationPermissionGranted){
init();
}
}
private static final String TAG = "MapActivity";
private static final String FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION;
private static final String COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION;
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1234;
private static final float DEFAULT_ZOOM = 15f;
//widgets
private AutoCompleteTextView mSearchText;
private boolean mLocationPermissionGranted = false;
private GoogleMap mMap;
private FusedLocationProviderClient mFusedLocationProviderClient;
private PlaceAutocompleteAdapter mplaceAutocompleteAdapter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
mSearchText = (AutoCompleteTextView) findViewById(R.id.input_search);
getLocationPermission();
}
private void init(){
mPlaceDetectionClient = Places.getPlaceDetectionClient(this);
mGeoDataClient = Places.getGeoDataClient(this);
mSearchText.setAdapter(mplaceAutocompleteAdapter);
Log.d(TAG, "init: initializing");
mplaceAutocompleteAdapter = new PlaceAutocompleteAdapter(this, mGeoDataClient, BOUNDS_GREATER_SYDNEY, null);
mSearchText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if(actionId == EditorInfo.IME_ACTION_SEARCH
|| actionId == EditorInfo.IME_ACTION_DONE
|| keyEvent.getAction() == KeyEvent.ACTION_DOWN
|| keyEvent.getAction() == KeyEvent.KEYCODE_ENTER){
//execute our method for searching
geoLocate();
}
return false;
}
});
}
private void geoLocate(){
Log.d(TAG, "geoLocate: geolocating");
String searchString = mSearchText.getText().toString();
Geocoder geocoder = new Geocoder(MapActivity.this);
List<Address> list = new ArrayList<>();
try{
list = geocoder.getFromLocationName(searchString, 1);
}catch (IOException e){
Log.e(TAG, "geoLocate: IOException: " + e.getMessage() );
}
if(list.size() > 0){
Address address = list.get(0);
Log.d(TAG, "geoLocate: found a location: " + address.toString());
//Toast.makeText(this, address.toString(), Toast.LENGTH_SHORT).show();
}
}
private void moveCamera(LatLng latLng, float zoom){
Log.d(TAG, "moveCamera: moving te camera to: lat" + latLng.latitude + ", lng: " + latLng.longitude);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom));
}
private void initMap(){
Log.d(TAG, "initMap: Initializing map");
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(MapActivity.this);
}
private void getLocationPermission(){
Log.d(TAG, "GetLocationPermission: getting location permissions");
String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION};
if(ContextCompat.checkSelfPermission(this.getApplicationContext(),
FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){
if(ContextCompat.checkSelfPermission(this.getApplicationContext(),
COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED){
mLocationPermissionGranted = true;
initMap();
}else {
ActivityCompat.requestPermissions(this,
permissions,LOCATION_PERMISSION_REQUEST_CODE);
}
}else {
ActivityCompat.requestPermissions(this,
permissions,LOCATION_PERMISSION_REQUEST_CODE);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
Log.d(TAG, "onRequestPermissionResult: called");
mLocationPermissionGranted = false;
switch (requestCode){
case LOCATION_PERMISSION_REQUEST_CODE:{
if(grantResults.length > 0){
for (int i = 0; i < grantResults.length; i++){
if (grantResults[i] != PackageManager.PERMISSION_GRANTED){
mLocationPermissionGranted = false;
Log.d(TAG, "onRequestPermissionResult: permission failed ");
return;
}
}
Log.d(TAG, "onRequestPermissionResult: permission failed ");
mLocationPermissionGranted = true;
//initialize our map
initMap();
}
}
}
}
}
Here is my app build gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.tw276.ghkjhk"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:support-media-compat:28.0.0 '
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.android.libraries.places:places-compat:1.0.0'
implementation 'com.google.android.gms:play-services-location:16.0.0'
implementation 'com.google.android.gms:play-services-maps:16.1.0'
implementation 'com.google.maps.android:android-maps-utils:0.5'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.android.volley:volley:1.1.1'
implementation 'com.android.support:design:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
and here is my adapter class:
import android.content.Context;
import android.graphics.Typeface;
import android.text.style.CharacterStyle;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.data.DataBufferUtils;
import com.google.android.gms.location.places.AutocompleteFilter;
import com.google.android.gms.location.places.AutocompletePrediction;
import com.google.android.gms.location.places.AutocompletePredictionBufferResponse;
import com.google.android.gms.location.places.GeoDataClient;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.tasks.RuntimeExecutionException;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Adapter that handles Autocomplete requests from the Places Geo Data Client.
* {#link AutocompletePrediction} results from the API are frozen and stored directly in this
* adapter. (See {#link AutocompletePrediction#freeze()}.)
*/
public class PlaceAutocompleteAdapter
extends ArrayAdapter<AutocompletePrediction> implements Filterable {
private static final String TAG = "PlaceAutocomplete";
private static final CharacterStyle STYLE_BOLD = new StyleSpan(Typeface.BOLD);
/**
* Current results returned by this adapter.
*/
private ArrayList<AutocompletePrediction> mResultList;
/**
* Handles autocomplete requests.
*/
private GeoDataClient mGeoDataClient;
/**
* The bounds used for Places Geo Data autocomplete API requests.
*/
private LatLngBounds mBounds;
/**
* The autocomplete filter used to restrict queries to a specific set of place types.
*/
private AutocompleteFilter mPlaceFilter;
/**
* Initializes with a resource for text rows and autocomplete query bounds.
*
* #see android.widget.ArrayAdapter#ArrayAdapter(android.content.Context, int)
*/
public PlaceAutocompleteAdapter(Context context, GeoDataClient geoDataClient,
LatLngBounds bounds, AutocompleteFilter filter) {
super(context, android.R.layout.simple_expandable_list_item_2, android.R.id.text1);
mGeoDataClient = geoDataClient;
mBounds = bounds;
mPlaceFilter = filter;
}
/**
* Sets the bounds for all subsequent queries.
*/
public void setBounds(LatLngBounds bounds) {
mBounds = bounds;
}
/**
* Returns the number of results received in the last autocomplete query.
*/
#Override
public int getCount() {
return mResultList.size();
}
/**
* Returns an item from the last autocomplete query.
*/
#Override
public AutocompletePrediction getItem(int position) {
return mResultList.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = super.getView(position, convertView, parent);
// Sets the primary and secondary text for a row.
// Note that getPrimaryText() and getSecondaryText() return a CharSequence that may contain
// styling based on the given CharacterStyle.
AutocompletePrediction item = getItem(position);
TextView textView1 = (TextView) row.findViewById(android.R.id.text1);
TextView textView2 = (TextView) row.findViewById(android.R.id.text2);
textView1.setText(item.getPrimaryText(STYLE_BOLD));
textView2.setText(item.getSecondaryText(STYLE_BOLD));
return row;
}
/**
* Returns the filter for the current set of autocomplete results.
*/
#Override
public Filter getFilter() {
return new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// We need a separate list to store the results, since
// this is run asynchronously.
ArrayList<AutocompletePrediction> filterData = new ArrayList<>();
// Skip the autocomplete query if no constraints are given.
if (constraint != null) {
// Query the autocomplete API for the (constraint) search string.
filterData = getAutocomplete(constraint);
}
results.values = filterData;
if (filterData != null) {
results.count = filterData.size();
} else {
results.count = 0;
}
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && results.count > 0) {
// The API returned at least one result, update the data.
mResultList = (ArrayList<AutocompletePrediction>) results.values;
notifyDataSetChanged();
} else {
// The API did not return any results, invalidate the data set.
notifyDataSetInvalidated();
}
}
#Override
public CharSequence convertResultToString(Object resultValue) {
// Override this method to display a readable result in the AutocompleteTextView
// when clicked.
if (resultValue instanceof AutocompletePrediction) {
return ((AutocompletePrediction) resultValue).getFullText(null);
} else {
return super.convertResultToString(resultValue);
}
}
};
}
/**
* Submits an autocomplete query to the Places Geo Data Autocomplete API.
* Results are returned as frozen AutocompletePrediction objects, ready to be cached.
* Returns an empty list if no results were found.
* Returns null if the API client is not available or the query did not complete
* successfully.
* This method MUST be called off the main UI thread, as it will block until data is returned
* from the API, which may include a network request.
*
* #param constraint Autocomplete query string
* #return Results from the autocomplete API or null if the query was not successful.
* #see GeoDataClient#getAutocompletePredictions(String, LatLngBounds, AutocompleteFilter)
* #see AutocompletePrediction#freeze()
*/
private ArrayList<AutocompletePrediction> getAutocomplete(CharSequence constraint) {
Log.i(TAG, "Starting autocomplete query for: " + constraint);
// Submit the query to the autocomplete API and retrieve a PendingResult that will
// contain the results when the query completes.
Task<AutocompletePredictionBufferResponse> results =
mGeoDataClient.getAutocompletePredictions(constraint.toString(), mBounds,
mPlaceFilter);
// This method should have been called off the main UI thread. Block and wait for at most
// 60s for a result from the API.
try {
Tasks.await(results, 60, TimeUnit.SECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
e.printStackTrace();
}
try {
AutocompletePredictionBufferResponse autocompletePredictions = results.getResult();
Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount()
+ " predictions.");
// Freeze the results immutable representation that can be stored safely.
return DataBufferUtils.freezeAndClose(autocompletePredictions);
} catch (RuntimeExecutionException e) {
// If the query did not complete successfully return null
Toast.makeText(getContext(), "Error contacting API: " + e.toString(),
Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error getting autocomplete prediction API call", e);
return null;
}
}
}
The exact class the PlaceAutocompleteAdapter constructor expects is
com.google.android.gms.location.places.GeoDataClient
While you're importing, erroneously
com.google.android.libraries.places.compat.GeoDataClient
inside your MapActivity class

I want to push a notification when exiting a Geofence in android studio

I am creating an app for my university that is supposed to push a notification when entering or exiting a geofence in android studio. The problem is that i can not think any way to code for sending a notification when exiting the geofence but the notification when entering a geofence works fine. here is my code thanks in advance
MainActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GeofenceController.getInstance().init(this);
NamedGeofense geofence = new NamedGeofense();
geofence.name ="abbott";
geofence.latitude =38.037847; //your lati and longii :)
geofence.longitude =23.70083;
geofence.radius =100;
GeofenceController.getInstance().addGeofence(geofence);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
NamedGeofence class
package com.example.adeeb.webviewfordigitalwaiter;
import com.google.android.gms.location.Geofence;
import java.util.UUID;
public class NamedGeofense {
public String id;
public String name;
public double latitude;
public double longitude;
public float radius;
// end region
// region Public
public Geofence geofence() {
id = UUID.randomUUID().toString();
// UUID is universal unique identifer. ref for you
// : https://docs.oracle.com/javase/7/docs/api/java/util/UUID.html
return new Geofence.Builder()
.setRequestId(id)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.setCircularRegion(latitude, longitude, radius)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build();
}
}
Geofence Controller class
package com.example.adeeb.webviewfordigitalwaiter;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class GeofenceController {
// region Properties
private final String TAG = GeofenceController.class.getName();
private Context context;
private GoogleApiClient googleApiClient;
private Gson gson;
private SharedPreferences prefs;
private GeofenceControllerListener listener;
private List<NamedGeofense> namedGeofences;
private Geofence geofenceToAdd;
private NamedGeofense namedGeofenceToAdd;
// endregion
// region Shared Instance
private static GeofenceController INSTANCE;
public static GeofenceController getInstance() {
if (INSTANCE == null) {
INSTANCE = new GeofenceController();
}
return INSTANCE;
}
// endregion
// region Public
//we call this from main activity (in main activity we mde an instance of geofence controller
// using static method getInstance() (like this GeofenceController.getInstance().init(this);)
public void init(Context context) {
this.context = context.getApplicationContext();
gson = new Gson();
// namedGeofences is arraylist of my defined class name (namedGeofences)
// i will discuss namedGeofences class .. wait a moment.. :P
namedGeofences = new ArrayList<>();
prefs = this.context.getSharedPreferences(Constants.SharedPrefs.Geofences, Context.MODE_PRIVATE);
// prefs is shared prefrence (shared is a class or local storage where we store data so that
// our data might not lost when we move to other class or activity)
// variable.we assign our shared prefrence name which is define in Constants class
//public static String Geofences = "SHARED_PREFS_GEOFENCES";
loadGeofences();
// loadGeofences(); is my define method where we load geofences data if they are store
// in our sharedprefrences
}
// region ConnectionCallbacks
private GoogleApiClient.ConnectionCallbacks connectionAddListener = new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Intent intent = new Intent(context, AreWeThereIntentService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingResult<Status> result = LocationServices.GeofencingApi.
addGeofences(googleApiClient, getAddGeofencingRequest(), pendingIntent);
result.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
saveGeofence();
} else {
Log.e(TAG, "Registering geofence failed: " + status.getStatusMessage() + " : " + status.getStatusCode());
sendError();
}
}
});
}
#Override
public void onConnectionSuspended(int i) {
Log.e(TAG, "Connecting to GoogleApiClient suspended.");
sendError();
}
};
public void addGeofence(NamedGeofense namedGeofence) {
this.namedGeofenceToAdd = namedGeofence;
this.geofenceToAdd = namedGeofence.geofence();
connectWithCallbacks(connectionAddListener);
}
// endregion
private GeofencingRequest getAddGeofencingRequest() {
List<Geofence> geofencesToAdd = new ArrayList<>();
geofencesToAdd.add(geofenceToAdd);
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(geofencesToAdd);
return builder.build();
}
private void saveGeofence() {
namedGeofences.add(namedGeofenceToAdd);
String json = gson.toJson(namedGeofenceToAdd);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(namedGeofenceToAdd.id, json);
editor.apply();
}
public interface GeofenceControllerListener {
void onGeofencesUpdated();
void onError();
}
public List<NamedGeofense> getNamedGeofences() {
return namedGeofences;
}
// endregion
// region Private
private void loadGeofences() {
// Map<String, ?> is A Map is a data structure consisting of a set of keys and values
// in which each key is mapped to a single value.
// we load geofences data if they are store
// in our sharedprefrences
Map<String, ?> keys = prefs.getAll();
// Loop over all geofence keys in prefs and add to namedGeofences
for (Map.Entry<String, ?> entry : keys.entrySet()) {
String jsonString = prefs.getString(entry.getKey(), null);
// gson is a library where we can store our whole class instance,
// below we assign previously store NamedGeofence class , and jsonString is key of that
// class
NamedGeofense namedGeofence = gson.fromJson(jsonString, NamedGeofense.class);
// than here we just add namedGeofence instance to our array list
namedGeofences.add(namedGeofence);
}
// Sort namedGeofences by name
}
private void connectWithCallbacks(GoogleApiClient.ConnectionCallbacks callbacks) {
googleApiClient = new GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
.addConnectionCallbacks(callbacks)
.addOnConnectionFailedListener(connectionFailedListener)
.build();
googleApiClient.connect();
}
private void sendError() {
if (listener != null) {
listener.onError();
}
}
// endregion
// region OnConnectionFailedListener
private GoogleApiClient.OnConnectionFailedListener connectionFailedListener = new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "Connecting to GoogleApiClient failed.");
sendError();
}
};
// endregion
// region Interfaces
// end region
}
Constant class
public class Constants {
public static class SharedPrefs {
public static String Geofences = "SHARED_PREFS_GEOFENCES";
}
}
ARE WE THERE YES SERVICE (HERES IS THE WHERE THE NOTIFICATIONS SHOULD BE PUT)
package com.example.adeeb.webviewfordigitalwaiter;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class AreWeThereIntentService extends IntentService {
// region Properties
private final String TAG = AreWeThereIntentService.class.getName();
private SharedPreferences prefs;
private Gson gson;
// endregion
// region Constructors
public AreWeThereIntentService() {
super("AreWeThereIntentService");
}
// endregion
// region Overrides
#Override
protected void onHandleIntent(Intent intent) {
prefs = getApplicationContext().getSharedPreferences(Constants.SharedPrefs.Geofences, Context.MODE_PRIVATE);
gson = new Gson();
Log.e(TAG, "on handle :)");
GeofencingEvent event = GeofencingEvent.fromIntent(intent);
if (event != null) {
Log.e(TAG, "event not null :) ");
if (event.hasError()) {
onError(event.getErrorCode());
} else {
int transition = event.getGeofenceTransition();
if (transition == Geofence.GEOFENCE_TRANSITION_ENTER || transition == Geofence.GEOFENCE_TRANSITION_EXIT) {
List<String> geofenceIds = new ArrayList<>();
for (Geofence geofence : event.getTriggeringGeofences()) {
geofenceIds.add(geofence.getRequestId());
}
if (transition == Geofence.GEOFENCE_TRANSITION_ENTER || transition == Geofence.GEOFENCE_TRANSITION_EXIT) {
onEnteredGeofences(geofenceIds);
Log.e(TAG, "transition enter :) ");
}
}
}
}
}
// endregion
// region Private
private void onEnteredGeofences(List<String> geofenceIds) {
Log.e(TAG, "on entergeofense: ");
for (String geofenceId : geofenceIds) {
String geofenceName = "";
// Loop over all geofence keys in prefs and retrieve NamedGeofence from SharedPreference
Map<String, ?> keys = prefs.getAll();
for (Map.Entry<String, ?> entry : keys.entrySet()) {
String jsonString = prefs.getString(entry.getKey(), null);
NamedGeofense namedGeofence = gson.fromJson(jsonString, NamedGeofense.class);
if (namedGeofence.id.equals(geofenceId)) {
geofenceName = namedGeofence.name;
break;
}
}
// Set the notification text and send the notification
//String contextText = String.format("you entered in meeting room", geofenceName);
NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, GeofenceMain.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingNotificationIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.pizaper2)
.setContentTitle("Welcome to geofense")
.setContentText("hello KOKALIS")
.setContentIntent(pendingNotificationIntent)
.setStyle(new NotificationCompat.BigTextStyle().bigText("hello KOKALIS"))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.build(); // id
notificationManager.notify(0, notification);
Log.e(TAG, "notifications: ");
}
}
private void onError(int i) {
Log.e(TAG, "Geofencing Error: " + i);
}
// endregion
}
In your NamedGeofence class, change .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER) to .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT). Otherwise, you'd only monitor the enter events.
Transition types should be set as a bit-wise OR, as stated here.

Categories