如何在 Android Studio 中构建 SOS 移动应用程序?
SOS 应用程序基本上是高级紧急应用程序,如果您和/或您的亲人发现自己处于危及生命的紧急情况并需要立即帮助,它们可以拯救您和/或您的亲人。当您需要一些个人帮助时,您实际上可以打开手机并呼叫或发送消息寻求帮助。但是在危及生命的紧急情况下,例如袭击、性侵犯、抢劫、骚扰、事故、火灾、助产,我们没有时间打开手机,相反,我们需要一些可访问的方法来寻求帮助无需实际操作手机。在本文中,我们将为 android 构建这样一个应用程序。
您能想出一些最简单的方法来刺激手机中的某些功能,而无需实际打开手机屏幕吗?一种方法是晃动手机。我们将创建一个服务,在该服务中,我们将通过电话监听震动事件。当我们注册一个摇晃事件时,即当用户摇晃手机时,我们会将用户的位置和一条预定义的消息发送给用户之前添加到应用程序的所有联系人。现在随着 Android 的每个版本,谷歌都对获取用户位置做出了一些严格的规定,这在数据安全方面也很重要。这使得为较新版本的 Android 创建这样的 SOS 应用程序变得困难。
先决条件
- 服务,
- 运行时权限管理,
- 填充列表视图,
- 广播接收器,
- Android中的数据库管理
分步实施
第 1 步:创建一个新项目
要在 Android Studio 中创建新项目,请参阅如何在 Android Studio 中创建/启动新项目。请注意,选择Java作为编程语言。
步骤 2:创建联系人模块
创建一个文件夹联系人,在此,我们将处理用户选择在紧急情况下发送消息的联系人填充 ListView 的所有文件。
步骤 2.1:为联系人创建模型类
创建一个模型类,该类将保存联系人的数据,主要是姓名和电话号码。除了通常的构造函数和 getter 和 setter,我们还有一个额外的 validate(String) 方法。此方法检查检索到的电话号码是否采用正确的格式(+91XXXXXXXXXX)。如果不是,则它转换字符串并返回格式化的字符串。下面是ContactModel的代码。 Java文件。
Java
public class ContactModel {
private int id;
private String phoneNo;
private String name;
// constructor
public ContactModel(int id, String name, String phoneNo) {
this.id = id;
this.phoneNo = validate(phoneNo);
this.name = name;
}
// validate the phone number, and reformat is necessary
private String validate(String phone) {
// creating StringBuilder for both the cases
StringBuilder case1 = new StringBuilder("+91");
StringBuilder case2 = new StringBuilder("");
// check if the string already has a "+"
if (phone.charAt(0) != '+') {
for (int i = 0; i < phone.length(); i++) {
// remove any spaces or "-"
if (phone.charAt(i) != '-' && phone.charAt(i) != ' ') {
case1.append(phone.charAt(i));
}
}
return case1.toString();
} else {
for (int i = 0; i < phone.length(); i++) {
// remove any spaces or "-"
if (phone.charAt(i) != '-' || phone.charAt(i) != ' ') {
case2.append(phone.charAt(i));
}
}
return case2.toString();
}
}
public String getPhoneNo() {
return phoneNo;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Java
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
import java.util.List;
public class DbHelper extends SQLiteOpenHelper {
// Database Version
private static final int DATABASE_VERSION = 1;
// Database Name
private static final String DATABASE_NAME = "contactdata";
// Country table name
private static final String TABLE_NAME= "contacts";
// Country Table Columns names
private static final String KEY_ID = "id";
private static final String NAME = "Name";
private static final String PHONENO = "PhoneNo";
public DbHelper(Context context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// create the table for the first time
String CREATE_COUNTRY_TABLE = "CREATE TABLE " + TABLE_NAME + "("
+ KEY_ID + " INTEGER PRIMARY KEY," + NAME + " TEXT,"
+ PHONENO + " TEXT" + ")";
db.execSQL(CREATE_COUNTRY_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
// method to add the contact
public void addcontact(ContactModel contact){
SQLiteDatabase db=this.getWritableDatabase();
ContentValues c=new ContentValues();
c.put(NAME,contact.getName());
c.put(PHONENO,contact.getPhoneNo());
db.insert(TABLE_NAME,null,c);
db.close();
}
// method to retrieve all the contacts in List
public List getAllContacts(){
List list=new ArrayList<>();
String query="SELECT * FROM "+TABLE_NAME;
SQLiteDatabase db=this.getReadableDatabase();
Cursor c=db.rawQuery(query,null);
if(c.moveToFirst()) {
do {
list.add(new ContactModel(c.getInt(0),c.getString(1),c.getString(2)));
} while (c.moveToNext());
}
return list;
}
// get the count of data, this will allow user
// to not add more that five contacts in database
public int count(){
int count=0;
String query="SELECT COUNT(*) FROM "+TABLE_NAME;
SQLiteDatabase db = this.getReadableDatabase();
Cursor c=db.rawQuery(query,null);
if(c.getCount()>0){
c.moveToFirst();
count=c.getInt(0);
}
c.close();
return count;
}
// Deleting single country
public void deleteContact(ContactModel contact) {
SQLiteDatabase db = this.getWritableDatabase();
int i=db.delete(TABLE_NAME,KEY_ID + " = ?",
new String[] { String.valueOf(contact.getId()) });
db.close();
}
}
Java
import android.content.Context;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.raghav.sos.R;
import java.util.List;
public class CustomAdapter extends ArrayAdapter {
Context context;
List contacts;
public CustomAdapter(@NonNull Context context, List contacts) {
super(context, 0, contacts);
this.context = context;
this.contacts = contacts;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// create a database helper object
// to handle the database manipulations
DbHelper db = new DbHelper(context);
// Get the data item for this position
ContactModel c = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_user, parent, false);
}
LinearLayout linearLayout = (LinearLayout) convertView.findViewById(R.id.linear);
// Lookup view for data population
TextView tvName = (TextView) convertView.findViewById(R.id.tvName);
TextView tvPhone = (TextView) convertView.findViewById(R.id.tvPhone);
// Populate the data into the template
// view using the data object
tvName.setText(c.getName());
tvPhone.setText(c.getPhoneNo());
linearLayout.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
// generate an MaterialAlertDialog Box
new MaterialAlertDialogBuilder(context)
.setTitle("Remove Contact")
.setMessage("Are you sure want to remove this contact?")
.setPositiveButton("YES", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// delete the specified contact from the database
db.deleteContact(c);
// remove the item from the list
contacts.remove(c);
// notify the listview that dataset has been changed
notifyDataSetChanged();
Toast.makeText(context, "Contact removed!", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("NO", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.show();
return false;
}
});
// Return the completed view to render on screen
return convertView;
}
// this method will update the ListView
public void refresh(List list) {
contacts.clear();
contacts.addAll(list);
notifyDataSetChanged();
}
}
XML
Java
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
public class ShakeDetector implements SensorEventListener {
/*
* The gForce that is necessary to register as shake.
* Must be greater than 1G (one earth gravity unit).
* You can install "G-Force", by Blake La Pierre
* from the Google Play Store and run it to see how
* many G's it takes to register a shake
*/
private static final float SHAKE_THRESHOLD_GRAVITY = 2.7F;
private static final int SHAKE_SLOP_TIME_MS = 500;
private static final int SHAKE_COUNT_RESET_TIME_MS = 3000;
private OnShakeListener mListener;
private long mShakeTimestamp;
private int mShakeCount;
public void setOnShakeListener(OnShakeListener listener) {
this.mListener = listener;
}
public interface OnShakeListener {
public void onShake(int count);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// ignore
}
@Override
public void onSensorChanged(SensorEvent event) {
if (mListener != null) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float gX = x / SensorManager.GRAVITY_EARTH;
float gY = y / SensorManager.GRAVITY_EARTH;
float gZ = z / SensorManager.GRAVITY_EARTH;
// gForce will be close to 1 when there is no movement.
Float f = new Float(gX * gX + gY * gY + gZ * gZ);
Double d = Math.sqrt(f.doubleValue());
float gForce = d.floatValue();
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
final long now = System.currentTimeMillis();
// ignore shake events too close to each other (500ms)
if (mShakeTimestamp + SHAKE_SLOP_TIME_MS > now) {
return;
}
// reset the shake count after 3 seconds of no shakes
if (mShakeTimestamp + SHAKE_COUNT_RESET_TIME_MS < now) {
mShakeCount = 0;
}
mShakeTimestamp = now;
mShakeCount++;
mListener.onShake(mShakeCount);
}
}
}
}
Java
@RequiresApi(Build.VERSION_CODES.O)
private void startMyOwnForeground()
{
String NOTIFICATION_CHANNEL_ID = "example.permanence";
String channelName = "Background Service";
NotificationChannel chan = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, channelName,
NotificationManager.IMPORTANCE_MIN);
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setContentTitle("You are protected.")
.setContentText("We are there for you")
// this is important, otherwise the
// notification will show the way you want i.e.
// it will show some default notification
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(2, notification);
}
Java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
Java
// method to vibrate the phone
public void vibrate()
{
final Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
VibrationEffect vibEff;
// Android Q and above have some predefined vibrating
// patterns
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
vibEff = VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK);
vibrator.cancel();
vibrator.vibrate(vibEff);
}
else {
vibrator.vibrate(500);
}
}
Java
FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(getApplicationContext());
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY, new CancellationToken() {
@Override
public boolean isCancellationRequested()
{
return false;
}
@NonNull
@Override
public CancellationToken onCanceledRequested(
@NonNull OnTokenCanceledListener
onTokenCanceledListener)
{
return null;
}
})
.addOnSuccessListener(
new OnSuccessListener() {
@Override
public void onSuccess(Location location)
{
// check if location is null
// for both the cases we will create
// different messages
if (location != null) {
...
}
else {
...
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e)
{
...
}
});
Java
SmsManager smsManager = SmsManager.getDefault();
DbHelper db = new DbHelper(SensorService.this);
List list = db.getAllContacts();
for (ContactModel c : list) {
String message
= "Hey, " + c.getName()
+ "I am in DANGER, i need help. Please urgently reach me out. Here are my coordinates.\n "
+ "http://maps.google.com/?q="
+ location.getLatitude() + ","
+ location.getLongitude();
smsManager.sendTextMessage(c.getPhoneNo(), null,
message, null, null);
}
Java
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.location.Location;
import android.os.Build;
import android.os.IBinder;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.telephony.SmsManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.CancellationToken;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.OnTokenCanceledListener;
import com.raghav.sos.Contacts.ContactModel;
import com.raghav.sos.Contacts.DbHelper;
import com.raghav.sos.R;
import java.util.List;
public class SensorService extends Service {
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private ShakeDetector mShakeDetector;
public SensorService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
// start the foreground service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startMyOwnForeground();
else
startForeground(1, new Notification());
// ShakeDetector initialization
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mShakeDetector = new ShakeDetector();
mShakeDetector.setOnShakeListener(new ShakeDetector.OnShakeListener() {
@SuppressLint("MissingPermission")
@Override
public void onShake(int count) {
// check if the user has shacked
// the phone for 3 time in a row
if (count == 3) {
// vibrate the phone
vibrate();
// create FusedLocationProviderClient to get the user location
FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(getApplicationContext());
// use the PRIORITY_BALANCED_POWER_ACCURACY
// so that the service doesn't use unnecessary power via GPS
// it will only use GPS at this very moment
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY, new CancellationToken() {
@Override
public boolean isCancellationRequested() {
return false;
}
@NonNull
@Override
public CancellationToken onCanceledRequested(@NonNull OnTokenCanceledListener onTokenCanceledListener) {
return null;
}
}).addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(Location location) {
// check if location is null
// for both the cases we will
// create different messages
if (location != null) {
// get the SMSManager
SmsManager smsManager = SmsManager.getDefault();
// get the list of all the contacts in Database
DbHelper db = new DbHelper(SensorService.this);
List list = db.getAllContacts();
// send SMS to each contact
for (ContactModel c : list) {
String message = "Hey, " + c.getName() + "I am in DANGER, i need help. Please urgently reach me out. Here are my coordinates.\n " + "http://maps.google.com/?q=" + location.getLatitude() + "," + location.getLongitude();
smsManager.sendTextMessage(c.getPhoneNo(), null, message, null, null);
}
} else {
String message = "I am in DANGER, i need help. Please urgently reach me out.\n" + "GPS was turned off.Couldn't find location. Call your nearest Police Station.";
SmsManager smsManager = SmsManager.getDefault();
DbHelper db = new DbHelper(SensorService.this);
List list = db.getAllContacts();
for (ContactModel c : list) {
smsManager.sendTextMessage(c.getPhoneNo(), null, message, null, null);
}
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d("Check: ", "OnFailure");
String message = "I am in DANGER, i need help. Please urgently reach me out.\n" + "GPS was turned off.Couldn't find location. Call your nearest Police Station.";
SmsManager smsManager = SmsManager.getDefault();
DbHelper db = new DbHelper(SensorService.this);
List list = db.getAllContacts();
for (ContactModel c : list) {
smsManager.sendTextMessage(c.getPhoneNo(), null, message, null, null);
}
}
});
}
}
});
// register the listener
mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
}
// method to vibrate the phone
public void vibrate() {
final Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
VibrationEffect vibEff;
// Android Q and above have some predefined vibrating patterns
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
vibEff = VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK);
vibrator.cancel();
vibrator.vibrate(vibEff);
} else {
vibrator.vibrate(500);
}
}
// For Build versions higher than Android Oreo, we launch
// a foreground service in a different way. This is due to the newly
// implemented strict notification rules, which require us to identify
// our own notification channel in order to view them correctly.
@RequiresApi(Build.VERSION_CODES.O)
private void startMyOwnForeground() {
String NOTIFICATION_CHANNEL_ID = "example.permanence";
String channelName = "Background Service";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_MIN);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setContentTitle("You are protected.")
.setContentText("We are there for you")
// this is important, otherwise the notification will show the way
// you want i.e. it will show some default notification
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(2, notification);
}
@Override
public void onDestroy() {
// create an Intent to call the Broadcast receiver
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("restartservice");
broadcastIntent.setClass(this, ReactivateService.class);
this.sendBroadcast(broadcastIntent);
super.onDestroy();
}
}
Java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
public class ReactivateService extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("Check: ","Receiver Started");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(new Intent(context, SensorService.class));
} else {
context.startService(new Intent(context, SensorService.class));
}
}
}
Java
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import com.raghav.sos.Contacts.ContactModel;
import com.raghav.sos.Contacts.CustomAdapter;
import com.raghav.sos.Contacts.DbHelper;
import com.raghav.sos.ShakeServices.ReactivateService;
import com.raghav.sos.ShakeServices.SensorService;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int IGNORE_BATTERY_OPTIMIZATION_REQUEST = 1002;
private static final int PICK_CONTACT = 1;
// create instances of various classes to be used
Button button1;
ListView listView;
DbHelper db;
List list;
CustomAdapter customAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// check for runtime permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.SEND_SMS, Manifest.permission.READ_CONTACTS}, 100);
}
}
// this is a special permission required only by devices using
// Android Q and above. The Access Background Permission is responsible
// for populating the dialog with "ALLOW ALL THE TIME" option
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
requestPermissions(new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 100);
}
// check for BatteryOptimization,
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
askIgnoreOptimization();
}
}
// start the service
SensorService sensorService = new SensorService();
Intent intent = new Intent(this, sensorService.getClass());
if (!isMyServiceRunning(sensorService.getClass())) {
startService(intent);
}
button1 = findViewById(R.id.Button1);
listView = (ListView) findViewById(R.id.ListView);
db = new DbHelper(this);
list = db.getAllContacts();
customAdapter = new CustomAdapter(this, list);
listView.setAdapter(customAdapter);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// calling of getContacts()
if (db.count() != 5) {
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, PICK_CONTACT);
} else {
Toast.makeText(MainActivity.this, "Can't Add more than 5 Contacts", Toast.LENGTH_SHORT).show();
}
}
});
}
// method to check if the service is running
private boolean isMyServiceRunning(Class> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
Log.i("Service status", "Running");
return true;
}
}
Log.i("Service status", "Not running");
return false;
}
@Override
protected void onDestroy() {
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("restartservice");
broadcastIntent.setClass(this, ReactivateService.class);
this.sendBroadcast(broadcastIntent);
super.onDestroy();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
Toast.makeText(this, "Permissions Denied!\n Can't use the App!", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// get the contact from the PhoneBook of device
switch (requestCode) {
case (PICK_CONTACT):
if (resultCode == Activity.RESULT_OK) {
Uri contactData = data.getData();
Cursor c = managedQuery(contactData, null, null, null, null);
if (c.moveToFirst()) {
String id = c.getString(c.getColumnIndexOrThrow(ContactsContract.Contacts._ID));
String hasPhone = c.getString(c.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
String phone = null;
try {
if (hasPhone.equalsIgnoreCase("1")) {
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + id, null, null);
phones.moveToFirst();
phone = phones.getString(phones.getColumnIndex("data1"));
}
String name = c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
db.addcontact(new ContactModel(0, name, phone));
list = db.getAllContacts();
customAdapter.refresh(list);
} catch (Exception ex) {
}
}
}
break;
}
}
// this method prompts the user to remove any
// battery optimisation constraints from the App
private void askIgnoreOptimization() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
@SuppressLint("BatteryLife") Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, IGNORE_BATTERY_OPTIMIZATION_REQUEST);
}
}
}
XML
XML
步骤 2.2:创建一个数据库助手类
我们需要将用户选择的所有联系人存储到数据库中,以便每次应用程序需要时都可以使用它。我们将在该数据库的帮助下填充 ListView,并且在发送消息时,我们将从该数据库中检索列表中的联系人。下面是DbHelper的代码。 Java文件。
Java
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
import java.util.List;
public class DbHelper extends SQLiteOpenHelper {
// Database Version
private static final int DATABASE_VERSION = 1;
// Database Name
private static final String DATABASE_NAME = "contactdata";
// Country table name
private static final String TABLE_NAME= "contacts";
// Country Table Columns names
private static final String KEY_ID = "id";
private static final String NAME = "Name";
private static final String PHONENO = "PhoneNo";
public DbHelper(Context context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// create the table for the first time
String CREATE_COUNTRY_TABLE = "CREATE TABLE " + TABLE_NAME + "("
+ KEY_ID + " INTEGER PRIMARY KEY," + NAME + " TEXT,"
+ PHONENO + " TEXT" + ")";
db.execSQL(CREATE_COUNTRY_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
// method to add the contact
public void addcontact(ContactModel contact){
SQLiteDatabase db=this.getWritableDatabase();
ContentValues c=new ContentValues();
c.put(NAME,contact.getName());
c.put(PHONENO,contact.getPhoneNo());
db.insert(TABLE_NAME,null,c);
db.close();
}
// method to retrieve all the contacts in List
public List getAllContacts(){
List list=new ArrayList<>();
String query="SELECT * FROM "+TABLE_NAME;
SQLiteDatabase db=this.getReadableDatabase();
Cursor c=db.rawQuery(query,null);
if(c.moveToFirst()) {
do {
list.add(new ContactModel(c.getInt(0),c.getString(1),c.getString(2)));
} while (c.moveToNext());
}
return list;
}
// get the count of data, this will allow user
// to not add more that five contacts in database
public int count(){
int count=0;
String query="SELECT COUNT(*) FROM "+TABLE_NAME;
SQLiteDatabase db = this.getReadableDatabase();
Cursor c=db.rawQuery(query,null);
if(c.getCount()>0){
c.moveToFirst();
count=c.getInt(0);
}
c.close();
return count;
}
// Deleting single country
public void deleteContact(ContactModel contact) {
SQLiteDatabase db = this.getWritableDatabase();
int i=db.delete(TABLE_NAME,KEY_ID + " = ?",
new String[] { String.valueOf(contact.getId()) });
db.close();
}
}
步骤 2.3:创建自定义适配器。Java
为了处理 ListView 中的数据,我们需要一个定制的适配器。我们将在 LinerLayout 上添加一个 LongClickListener,这样每当用户想要从 ListView 中删除现有项目时,他只需长按该项目即可。作为回报,我们会显示一个对话框,要求确认。当用户确认后,我们也将从数据库中删除该项目。下面是CustomAdapter的代码。 Java文件。
Java
import android.content.Context;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.raghav.sos.R;
import java.util.List;
public class CustomAdapter extends ArrayAdapter {
Context context;
List contacts;
public CustomAdapter(@NonNull Context context, List contacts) {
super(context, 0, contacts);
this.context = context;
this.contacts = contacts;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// create a database helper object
// to handle the database manipulations
DbHelper db = new DbHelper(context);
// Get the data item for this position
ContactModel c = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_user, parent, false);
}
LinearLayout linearLayout = (LinearLayout) convertView.findViewById(R.id.linear);
// Lookup view for data population
TextView tvName = (TextView) convertView.findViewById(R.id.tvName);
TextView tvPhone = (TextView) convertView.findViewById(R.id.tvPhone);
// Populate the data into the template
// view using the data object
tvName.setText(c.getName());
tvPhone.setText(c.getPhoneNo());
linearLayout.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
// generate an MaterialAlertDialog Box
new MaterialAlertDialogBuilder(context)
.setTitle("Remove Contact")
.setMessage("Are you sure want to remove this contact?")
.setPositiveButton("YES", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// delete the specified contact from the database
db.deleteContact(c);
// remove the item from the list
contacts.remove(c);
// notify the listview that dataset has been changed
notifyDataSetChanged();
Toast.makeText(context, "Contact removed!", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("NO", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.show();
return false;
}
});
// Return the completed view to render on screen
return convertView;
}
// this method will update the ListView
public void refresh(List list) {
contacts.clear();
contacts.addAll(list);
notifyDataSetChanged();
}
}
步骤 2.4:item_user.xml
ListView 中每个项目的布局文件。
XML
步骤 3:创建服务模块
该模块将包含抖动检测、运行服务和注册广播接收器的所有必要功能。
步骤 3.1:创建 ShakeDetector 类
这里我们实现了 SensorEventListener,当传感器数据有新的或变化时,它用于从 SensorManager 接收通知。现在,为了注册震动事件,用户震动手机时传感器体验的G-Force必须大于1G。这是因为在某些情况下,手机可能会在口袋中或汽车中等时晃动。为了解决这个缺点,我们包括了一个计数机制,可以计算晃动的次数,即如果用户晃动设备连续 3 次,那么我们将注册一个震动事件。为此,两次连续抖动之间的时间应该最短,即大约 500 毫秒。我们还将在 3 秒不活动后使摇晃计数为零。这将允许用户再次摇晃手机以发送消息。下面是ShakeDetector的代码。 Java文件。
Java
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
public class ShakeDetector implements SensorEventListener {
/*
* The gForce that is necessary to register as shake.
* Must be greater than 1G (one earth gravity unit).
* You can install "G-Force", by Blake La Pierre
* from the Google Play Store and run it to see how
* many G's it takes to register a shake
*/
private static final float SHAKE_THRESHOLD_GRAVITY = 2.7F;
private static final int SHAKE_SLOP_TIME_MS = 500;
private static final int SHAKE_COUNT_RESET_TIME_MS = 3000;
private OnShakeListener mListener;
private long mShakeTimestamp;
private int mShakeCount;
public void setOnShakeListener(OnShakeListener listener) {
this.mListener = listener;
}
public interface OnShakeListener {
public void onShake(int count);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// ignore
}
@Override
public void onSensorChanged(SensorEvent event) {
if (mListener != null) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float gX = x / SensorManager.GRAVITY_EARTH;
float gY = y / SensorManager.GRAVITY_EARTH;
float gZ = z / SensorManager.GRAVITY_EARTH;
// gForce will be close to 1 when there is no movement.
Float f = new Float(gX * gX + gY * gY + gZ * gZ);
Double d = Math.sqrt(f.doubleValue());
float gForce = d.floatValue();
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
final long now = System.currentTimeMillis();
// ignore shake events too close to each other (500ms)
if (mShakeTimestamp + SHAKE_SLOP_TIME_MS > now) {
return;
}
// reset the shake count after 3 seconds of no shakes
if (mShakeTimestamp + SHAKE_COUNT_RESET_TIME_MS < now) {
mShakeCount = 0;
}
mShakeTimestamp = now;
mShakeCount++;
mListener.onShake(mShakeCount);
}
}
}
}
步骤 3.2:创建 SensorService
创建传感器服务。从 Android 6 开始,谷歌就包含了一些关于后台服务的额外安全检查。现在对服务的处理与以前完全不同。
我们在构建这个应用程序时的主要关注点应该是如何在应用程序没有运行时(甚至从最近的应用程序中删除)也能保持服务运行。
对于在宿主应用程序死机时运行的服务,称为在后台运行的服务。为了让服务在后台运行,我们需要一些额外的权限。在 Android O 及以上版本中,我们不能有后台服务,而是可以使用前台服务。前台服务执行用户可以注意到的操作。
必须为每个前台操作显示优先级为 PRIORITY LOW 或更高的状态栏警告。用户会意识到应用程序在前台运行并以这种方式使用机器资源。除非服务中断或退出前台,否则消息不可忽略。
Java
@RequiresApi(Build.VERSION_CODES.O)
private void startMyOwnForeground()
{
String NOTIFICATION_CHANNEL_ID = "example.permanence";
String channelName = "Background Service";
NotificationChannel chan = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, channelName,
NotificationManager.IMPORTANCE_MIN);
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setContentTitle("You are protected.")
.setContentText("We are there for you")
// this is important, otherwise the
// notification will show the way you want i.e.
// it will show some default notification
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(2, notification);
}
如果您以 START STICKY 返回类型启动服务,即使主机活动未在前台运行,它也会在后台运行。如果Android由于内存错误或其他原因不得不强行关闭程序,服务将在无需用户干预的情况下重新启动。
Java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
为了让用户知道 Shake 事件已经注册或者说消息已经发送,我们创建了一个 vibrate() 方法。这将使手机以定义的波形格式振动。
Java
// method to vibrate the phone
public void vibrate()
{
final Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
VibrationEffect vibEff;
// Android Q and above have some predefined vibrating
// patterns
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
vibEff = VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK);
vibrator.cancel();
vibrator.vibrate(vibEff);
}
else {
vibrator.vibrate(500);
}
}
现在,为了获取用户位置,我们将使用 FusedLocationProviderClient。 FusedLocationProviderClient 有一个名为 getCurremtLocation() 的函数。当被要求时,此方法提供用户的当前位置。但是这种方法需要开启手机的GPS。否则,它将返回一个空位置。从 Android O 及更高版本,为了获取位置或任何会向应用程序显示用户位置的内容,位置服务或 GPS 应该打开。因此,用户知道应用程序的位置使用情况。
Java
FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(getApplicationContext());
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY, new CancellationToken() {
@Override
public boolean isCancellationRequested()
{
return false;
}
@NonNull
@Override
public CancellationToken onCanceledRequested(
@NonNull OnTokenCanceledListener
onTokenCanceledListener)
{
return null;
}
})
.addOnSuccessListener(
new OnSuccessListener() {
@Override
public void onSuccess(Location location)
{
// check if location is null
// for both the cases we will create
// different messages
if (location != null) {
...
}
else {
...
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e)
{
...
}
});
此外,当我们成功检索位置时,我们会创建一个 SMSManager 对象,该对象将帮助我们向数据库中的所有联系人发送消息。如果用户的位置服务未开启,我们可以生成不带坐标的不同消息。以便接收紧急消息的人知道主机设备当时没有开启定位服务。然后,他可以直接与附近的警察局协调,后者可以跟踪此人的设备位置。
Java
SmsManager smsManager = SmsManager.getDefault();
DbHelper db = new DbHelper(SensorService.this);
List list = db.getAllContacts();
for (ContactModel c : list) {
String message
= "Hey, " + c.getName()
+ "I am in DANGER, i need help. Please urgently reach me out. Here are my coordinates.\n "
+ "http://maps.google.com/?q="
+ location.getLatitude() + ","
+ location.getLongitude();
smsManager.sendTextMessage(c.getPhoneNo(), null,
message, null, null);
}
到目前为止,我们所做的一切都将起作用,直到活动处于前台或运行状态。但是当用户杀死应用程序时怎么办?还是锁手机?为此,我们创建了一个BroadcastReceiver 。
下面是SensorService的完整代码。 Java文件。
Java
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.location.Location;
import android.os.Build;
import android.os.IBinder;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.telephony.SmsManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.CancellationToken;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.OnTokenCanceledListener;
import com.raghav.sos.Contacts.ContactModel;
import com.raghav.sos.Contacts.DbHelper;
import com.raghav.sos.R;
import java.util.List;
public class SensorService extends Service {
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private ShakeDetector mShakeDetector;
public SensorService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
// start the foreground service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startMyOwnForeground();
else
startForeground(1, new Notification());
// ShakeDetector initialization
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mShakeDetector = new ShakeDetector();
mShakeDetector.setOnShakeListener(new ShakeDetector.OnShakeListener() {
@SuppressLint("MissingPermission")
@Override
public void onShake(int count) {
// check if the user has shacked
// the phone for 3 time in a row
if (count == 3) {
// vibrate the phone
vibrate();
// create FusedLocationProviderClient to get the user location
FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(getApplicationContext());
// use the PRIORITY_BALANCED_POWER_ACCURACY
// so that the service doesn't use unnecessary power via GPS
// it will only use GPS at this very moment
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY, new CancellationToken() {
@Override
public boolean isCancellationRequested() {
return false;
}
@NonNull
@Override
public CancellationToken onCanceledRequested(@NonNull OnTokenCanceledListener onTokenCanceledListener) {
return null;
}
}).addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(Location location) {
// check if location is null
// for both the cases we will
// create different messages
if (location != null) {
// get the SMSManager
SmsManager smsManager = SmsManager.getDefault();
// get the list of all the contacts in Database
DbHelper db = new DbHelper(SensorService.this);
List list = db.getAllContacts();
// send SMS to each contact
for (ContactModel c : list) {
String message = "Hey, " + c.getName() + "I am in DANGER, i need help. Please urgently reach me out. Here are my coordinates.\n " + "http://maps.google.com/?q=" + location.getLatitude() + "," + location.getLongitude();
smsManager.sendTextMessage(c.getPhoneNo(), null, message, null, null);
}
} else {
String message = "I am in DANGER, i need help. Please urgently reach me out.\n" + "GPS was turned off.Couldn't find location. Call your nearest Police Station.";
SmsManager smsManager = SmsManager.getDefault();
DbHelper db = new DbHelper(SensorService.this);
List list = db.getAllContacts();
for (ContactModel c : list) {
smsManager.sendTextMessage(c.getPhoneNo(), null, message, null, null);
}
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d("Check: ", "OnFailure");
String message = "I am in DANGER, i need help. Please urgently reach me out.\n" + "GPS was turned off.Couldn't find location. Call your nearest Police Station.";
SmsManager smsManager = SmsManager.getDefault();
DbHelper db = new DbHelper(SensorService.this);
List list = db.getAllContacts();
for (ContactModel c : list) {
smsManager.sendTextMessage(c.getPhoneNo(), null, message, null, null);
}
}
});
}
}
});
// register the listener
mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
}
// method to vibrate the phone
public void vibrate() {
final Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
VibrationEffect vibEff;
// Android Q and above have some predefined vibrating patterns
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
vibEff = VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK);
vibrator.cancel();
vibrator.vibrate(vibEff);
} else {
vibrator.vibrate(500);
}
}
// For Build versions higher than Android Oreo, we launch
// a foreground service in a different way. This is due to the newly
// implemented strict notification rules, which require us to identify
// our own notification channel in order to view them correctly.
@RequiresApi(Build.VERSION_CODES.O)
private void startMyOwnForeground() {
String NOTIFICATION_CHANNEL_ID = "example.permanence";
String channelName = "Background Service";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_MIN);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setContentTitle("You are protected.")
.setContentText("We are there for you")
// this is important, otherwise the notification will show the way
// you want i.e. it will show some default notification
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(2, notification);
}
@Override
public void onDestroy() {
// create an Intent to call the Broadcast receiver
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("restartservice");
broadcastIntent.setClass(this, ReactivateService.class);
this.sendBroadcast(broadcastIntent);
super.onDestroy();
}
}
步骤 3.3:创建广播接收器
每当服务被销毁时,就会调用 onDestroy 方法,我们将使用该方法在服务实际销毁之前调用广播接收器。
作为回报,广播接收器再次启动服务。我们的问题解决了!!该服务现在在没有主机活动的情况下在后台运行。
下面是ReactivateService的完整代码。 Java文件。
Java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
public class ReactivateService extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("Check: ","Receiver Started");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(new Intent(context, SensorService.class));
} else {
context.startService(new Intent(context, SensorService.class));
}
}
}
第 4 步:使用MainActivity
转到主活动。 Java文件,参考如下代码。下面是MainActivity的代码。 Java文件。代码中添加了注释以更详细地理解代码。
Java
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import com.raghav.sos.Contacts.ContactModel;
import com.raghav.sos.Contacts.CustomAdapter;
import com.raghav.sos.Contacts.DbHelper;
import com.raghav.sos.ShakeServices.ReactivateService;
import com.raghav.sos.ShakeServices.SensorService;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int IGNORE_BATTERY_OPTIMIZATION_REQUEST = 1002;
private static final int PICK_CONTACT = 1;
// create instances of various classes to be used
Button button1;
ListView listView;
DbHelper db;
List list;
CustomAdapter customAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// check for runtime permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.SEND_SMS, Manifest.permission.READ_CONTACTS}, 100);
}
}
// this is a special permission required only by devices using
// Android Q and above. The Access Background Permission is responsible
// for populating the dialog with "ALLOW ALL THE TIME" option
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
requestPermissions(new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 100);
}
// check for BatteryOptimization,
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
askIgnoreOptimization();
}
}
// start the service
SensorService sensorService = new SensorService();
Intent intent = new Intent(this, sensorService.getClass());
if (!isMyServiceRunning(sensorService.getClass())) {
startService(intent);
}
button1 = findViewById(R.id.Button1);
listView = (ListView) findViewById(R.id.ListView);
db = new DbHelper(this);
list = db.getAllContacts();
customAdapter = new CustomAdapter(this, list);
listView.setAdapter(customAdapter);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// calling of getContacts()
if (db.count() != 5) {
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, PICK_CONTACT);
} else {
Toast.makeText(MainActivity.this, "Can't Add more than 5 Contacts", Toast.LENGTH_SHORT).show();
}
}
});
}
// method to check if the service is running
private boolean isMyServiceRunning(Class> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
Log.i("Service status", "Running");
return true;
}
}
Log.i("Service status", "Not running");
return false;
}
@Override
protected void onDestroy() {
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("restartservice");
broadcastIntent.setClass(this, ReactivateService.class);
this.sendBroadcast(broadcastIntent);
super.onDestroy();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
Toast.makeText(this, "Permissions Denied!\n Can't use the App!", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// get the contact from the PhoneBook of device
switch (requestCode) {
case (PICK_CONTACT):
if (resultCode == Activity.RESULT_OK) {
Uri contactData = data.getData();
Cursor c = managedQuery(contactData, null, null, null, null);
if (c.moveToFirst()) {
String id = c.getString(c.getColumnIndexOrThrow(ContactsContract.Contacts._ID));
String hasPhone = c.getString(c.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
String phone = null;
try {
if (hasPhone.equalsIgnoreCase("1")) {
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + id, null, null);
phones.moveToFirst();
phone = phones.getString(phones.getColumnIndex("data1"));
}
String name = c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
db.addcontact(new ContactModel(0, name, phone));
list = db.getAllContacts();
customAdapter.refresh(list);
} catch (Exception ex) {
}
}
}
break;
}
}
// this method prompts the user to remove any
// battery optimisation constraints from the App
private void askIgnoreOptimization() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
@SuppressLint("BatteryLife") Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, IGNORE_BATTERY_OPTIMIZATION_REQUEST);
}
}
}
导航到app > res > layout > activity_main.xml并将以下代码添加到该文件中。下面是activity_main.xml文件的代码。
XML
第 5 步:使用 AndroidManifest.xml
XML
输出:
未来范围
- 您可以添加个人援助紧急情况的选项,例如显示附近警察局、医院、出租车等的地图。
- 您创建一个逻辑,每 1 或 2 分钟发送一次用户位置,而用户无需再次摇晃设备。
- 您可以添加语音或/和视频录制功能。
- 此外,您可以在摇晃时向多人或单个人添加呼叫。
- 您可以尝试添加 OpenCellId,这将允许您获取最近的移动塔的位置。但再次记住,为了计算位置,您需要一些设备信息(无 GPS),这需要打开 GPS 或定位服务。
- 改变用户界面的大门总是敞开的。
Notes:
1. Allow the app to autostart, in order to use the app while the screen is off.
2. Remove any battery optimization constraints on the app. This might make Android kill the service.
3. Allow all the permissions, especially allow location permissions by Allowing the app to use the device location all the time. This would allow the service to use the device location when the shake event is registered.
GitHub 链接: https : //github.com/raghavtilak/SOS