/*
* Copyright (C) 2012 Louis Fazen
*
* 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.alphabetbloc.accessadmin.services;
import java.util.ArrayList;
import java.util.Collections;
import android.app.AlarmManager;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
import android.os.PowerManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.alphabetbloc.accessadmin.R;
import com.alphabetbloc.accessadmin.activities.MessageHoldActivity;
import com.alphabetbloc.accessadmin.activities.SetAppPreferences;
import com.alphabetbloc.accessadmin.activities.SetUserPassword;
import com.alphabetbloc.accessadmin.data.Constants;
import com.alphabetbloc.accessadmin.data.EncryptedPreferences;
import com.alphabetbloc.accessadmin.data.Policy;
import com.alphabetbloc.accessadmin.data.StringGenerator;
import com.alphabetbloc.accessadmin.receivers.DeviceAdmin;
import com.alphabetbloc.accessadmin.receivers.LockPhoneReceiver;
import com.commonsware.cwac.wakeful.WakefulIntentService;
import com.commonsware.cwac.wakeful.WakelockWorkListener;
import com.commonsware.cwac.wakeful.WakelockWorkReceiver;
/**
* Service used to conduct all device admin work. The service holds a wakelock
* while doing device admin work by extending @CommonsWare's
* WakefulIntentService. The service takes care of scheduling an alarm to
* continue the device admin work even if there is a reboot or if the process is
* killed. The alarm will continue to wake the device at the time specified in
* the WakelockListener, and try to complete the work through a wakelock on
* boot. The service will then cancel its own alarms on successful completion of
* its intent.
*
* <p>
* DEVICE_ADMIN_WORK type extras: <br>
* 1. SEND_SMS: sends SMS with a repeat alarm until sent 2. SEND_GPS: Sending an
* SMS with GPS coordinates<br>
* 3. VERIFY_SIM: Sending SMS with new serial and line when SIM is changed<br>
* 4. LOCK_SCREEN: Locks the Screen<br>
* 5. WIPE_ODK_DATA: Wiping the patient sensitive data<br>
* 6. WIPE_DATA: Wiping the entire device to factory reset (will allow user to
* setup new device)<br>
* 7. RESET_TO_DEFAULT_PWD: Resetting password to a default, depends on what the
* password quality is<br>
* 8. LOCK_RANDOM_PWD: Resetting password to a random string (so as to
* permanently lock device until reset password to default)<br>
* 9. HOLD_DEVICE: Starting MessageHoldActivity to send message to user before
* e.g. locking the device.<br>
* 10. CANCEL_ALARMS: Reset all alarms<br>
* <p>
* To use this service and ensure a wakelock, do not call directly, but call
* through WakefulIntentService. First, create an intent for
* DeviceAdminService.class and add intent extras to resolve the action for
* DeviceAdminService. Then pass the intent through the sendWakefulWork method:
* <br>
* <br>
* <b>Example:</b>
* <p>
* Intent i = new Intent(mContext, DeviceAdminService.class);<br>
* i.putExtra(Constants.DEVICE_ADMIN_WORK, deviceAdminAction); <br>
* WakefulIntentService.sendWakefulWork(mContext, i);<br>
* </p>
*
* @author Louis.Fazen@gmail.com
*
*/
public class DeviceAdminService extends WakefulIntentService {
DevicePolicyManager mDPM;
ComponentName mDeviceAdmin;
SharedPreferences mPrefs;
Policy mPolicy;
private Context mContext;
private int mAirplaneCount = 0;
private static final String TAG = DeviceAdminService.class.getSimpleName();
private static final int SIM_VERIFIED = 1;
private static final int SIM_MISSING = 2;
private static final int SIM_CHANGED = 3;
private boolean mResetAlarm;
public DeviceAdminService() {
super("AppService");
}
@Override
protected void doWakefulWork(Intent intent) {
if (Constants.DEBUG)
Log.d(TAG, "DeviceAdminService is Called");
mContext = getApplicationContext();
mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
mDeviceAdmin = new ComponentName(DeviceAdminService.this, DeviceAdmin.class);
mPolicy = new Policy(mContext);
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
resolveIntent(intent);
}
private void resolveIntent(Intent intent) {
String smsLine;
String smsMessage;
int standingIntent = mPrefs.getInt(Constants.SAVED_DEVICE_ADMIN_WORK, 0);
int smsIntent = intent.getIntExtra(Constants.DEVICE_ADMIN_WORK, 0);
if (Constants.DEBUG)
Log.v(TAG, "DeviceAdminService Called with \n\tStandingIntent=" + standingIntent + " \n\tNewSMSIntent=" + smsIntent);
// May be new Intent or called from BOOT... so resolve intent:
if (smsIntent != 0) {
// we have a new intent from SMS
smsLine = intent.getStringExtra(Constants.SMS_LINE);
smsMessage = intent.getStringExtra(Constants.SMS_MESSAGE);
if (smsLine == null)
smsLine = mPrefs.getString(Constants.SMS_REPLY_LINE, Constants.DEFAULT_SMS_REPLY_LINE);
if (smsMessage == null)
smsMessage = "";
// Reset alarm only for higher order intents
if (smsIntent >= standingIntent)
resetAlarm(smsIntent, smsLine, smsMessage);
else
mResetAlarm = false;
} else {
// Service called by alarm or boot, so recreate intent from saved
// (after boot or kill, intent extras would be lost)
// do not reset alarms
smsIntent = mPrefs.getInt(Constants.SAVED_DEVICE_ADMIN_WORK, 0);
if (Constants.DEBUG)
Log.d(TAG, "DeviceAdminService is Called with smsIntentSaved=" + smsIntent);
smsLine = mPrefs.getString(Constants.SAVED_SMS_LINE, Constants.DEFAULT_SMS_REPLY_LINE);
smsMessage = mPrefs.getString(Constants.SAVED_SMS_MESSAGE, "");
// ALWAYS Reset the Alarm if CancelAdminAlarms is called from Alarm
// or Boot because there should be no other alarms saved
mResetAlarm = true;
}
switch (smsIntent) {
case Constants.LOCK_SCREEN:
lockDevice();
break;
case Constants.HOLD_DEVICE:
holdAdminMessage(smsMessage);
break;
case Constants.LOCK_RANDOM_PWD:
lockPromptPassword();
break;
case Constants.HOLD_DEVICE_LOCKED:
holdDeviceLocked();
break;
case Constants.STOP_HOLD_DEVICE:
stopHoldDevice();
break;
case Constants.EDIT_ACCESS_MRS_PREF:
editAccessMrsPreference(smsMessage);
break;
case Constants.RESET_PWD_TO_SMS_PWD:
resetPromptPassword(smsMessage);
break;
case Constants.RESET_TO_DEFAULT_PWD:
resetPromptPassword();
break;
case Constants.SEND_SMS:
sendRepeatingSMS(smsIntent, smsLine, smsMessage);
break;
case Constants.SEND_GPS:
sendGPSCoordinates();
break;
case Constants.RESET_ADMIN_ID:
resetSmsAdminId();
break;
case Constants.SEND_ADMIN_ID:
smsAdminId();
break;
case Constants.VERIFY_SIM:
verifySIMCode();
break;
case Constants.WIPE_ODK_DATA:
wipeOdkData();
break;
case Constants.WIPE_DATA:
wipeDevice();
break;
case Constants.FACTORY_RESET:
factoryReset();
break;
case Constants.CANCEL_ALARMS:
cancelAdminAlarms(smsMessage);
break;
default:
cancelAdminAlarms(null);
if (Constants.DEBUG)
Log.e(TAG, "No Intent Received or Saved.");
break;
}
}
private void editAccessMrsPreference(String smsMessage) {
int equals = smsMessage.indexOf("=");
String preferenceKey = smsMessage.substring(0, equals);
String preferenceValue = smsMessage.substring(equals + 1);
Intent i = new Intent(SetAppPreferences.ACCESS_MRS_SET_PREFERENCE);
i.putExtra(SetAppPreferences.PREFERENCE_KEY, preferenceKey);
i.putExtra(SetAppPreferences.PREFERENCE_VALUE, preferenceValue);
sendBroadcast(i);
cancelAdminAlarms("Requested change to AccessMRS preference \'" + preferenceKey + "\'.");
}
/**
* Holds the device in an activity the user can not leave, and posts a wait
* message to the user in a dialog box.
*
*/
private void holdAdminMessage(String message) {
holdDevice(Constants.ADMIN_MESSAGE, message, null, null);
// confirm that this worked before canceling the alarm
android.os.SystemClock.sleep(1000 * 5);
if (MessageHoldActivity.sMessageHoldActive)
cancelAdminAlarms("Now holding device with admin message.");
else
sendSingleSMS("Unable to hold device. Alarm is still active.");
}
/**
* WARNING! PERMANENT! DOES NOT CANCEL ALARMS! THIS CAN ONLY BE RESET BY
* ADMIN.
*
* Convenience Method for holdDevice(int, message, message, message) to
* holds the device with the device locked message.
*/
private void holdDeviceLocked() {
// Dont repeat the intent if already active
if (MessageHoldActivity.sMessageHoldActive && MessageHoldActivity.sHoldType == Constants.DEVICE_LOCKED)
return;
if (MessageHoldActivity.sMessageHoldActive && MessageHoldActivity.sPermanentHold) {
if (Constants.DEBUG)
Log.e(TAG, "Updating ongoing activity with HoldType=" + MessageHoldActivity.sHoldType);
// Update current activity if already locked in another activity type
MessageHoldActivity.sHoldType = Constants.DEVICE_LOCKED;
MessageHoldActivity.sMessage = getString(R.string.locked_message);
MessageHoldActivity.sSubMessage = getString(R.string.return_thin_message);
MessageHoldActivity.sAdditionalInfo = null;
} else {
// Start new activity
Intent i = new Intent(mContext, MessageHoldActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(Constants.HOLD_TYPE, Constants.DEVICE_LOCKED);
i.putExtra(MessageHoldActivity.MESSAGE, getString(R.string.locked_message));
i.putExtra(MessageHoldActivity.SUBMESSAGE, getString(R.string.return_thin_message));
mContext.startActivity(i);
}
// confirm that this worked before canceling the alarm
android.os.SystemClock.sleep(1000 * 5);
if (MessageHoldActivity.sMessageHoldActive)
sendSingleSMS("Currently holding device. Alarm is still active.");
else
sendSingleSMS("Unable to hold device. Alarm is still active.");
}
/**
* WARNING! PERMANENT! DOES NOT CANCEL ALARMS! THIS CAN ONLY BE RESET BY
* ADMIN.
*
* Holds the device in an activity the user can not leave, and posts one or
* more messages for the user. The view (holdType) may either be an admin
* message, or alarm view.
*
*
*
*/
private void holdDevice(int holdType, String message, String subMessage, String additionalInfo) {
if (Constants.DEBUG)
Log.e(TAG, "Hold Device is being called with MessageHoldType=" + holdType);
Intent i = new Intent(mContext, MessageHoldActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
i.putExtra(Constants.HOLD_TYPE, holdType);
if (message != null)
i.putExtra(MessageHoldActivity.MESSAGE, message);
if (subMessage != null)
i.putExtra(MessageHoldActivity.SUBMESSAGE, subMessage);
if (additionalInfo != null)
i.putExtra(MessageHoldActivity.ADDITIONAL_INFO, additionalInfo);
mContext.startActivity(i);
}
private void stopHoldDevice() {
if (Constants.DEBUG)
Log.e(TAG, "StopHoldDevice is being called.");
Intent i = new Intent(mContext, MessageHoldActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(MessageHoldActivity.STOP_HOLD, true);
mContext.startActivity(i);
// confirm that this worked before canceling the alarm
android.os.SystemClock.sleep(1000 * 5);
if (!MessageHoldActivity.sMessageHoldActive)
cancelAdminAlarms("Stopped device hold.");
else
sendSingleSMS("Unable to stop device hold. Alarm is still active.");
}
/**
* Locks the device but does not change the password. Sends an confirmation
* SMS to the reporting line.
*
*/
public void lockDevice() {
if (Constants.DEBUG)
Log.d(TAG, "locking the device");
mDPM.lockNow();
// confirm that this worked before canceling the alarm
android.os.SystemClock.sleep(1000 * 2);
if (isDeviceLocked())
cancelAdminAlarms("Locked device.");
else
sendSingleSMS("Unable to lock device. Alarm is still active.");
}
/**
* Check whether device is locked (either screen asleep or on lock screen).
*
* @return true if device is locked
*/
public boolean isDeviceLocked() {
KeyguardManager myKM = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mPolicy.isActivePasswordSufficient();
if (myKM.inKeyguardRestrictedInputMode() && mPolicy.isActivePasswordSufficient()) {
if (Constants.DEBUG)
Log.d(TAG, "screen is locked");
return true;
} else if (!pm.isScreenOn() && mPolicy.isActivePasswordSufficient()) {
if (Constants.DEBUG)
Log.d(TAG, "screen is off and password protected.");
return true;
} else {
return false;
}
}
/**
* WARNING! THIS IS PERMANENT. DOES NOT CANCEL ALARMS BECAUSE IT WIPES
* ENTIRE DEVICE.
*
* Factory reset. This method is not called directly, but only after patient
* data has been wiped.
*/
private void factoryReset() {
sendSingleSMS("Performing a Factory Reset");
// wait a minute for SMS to send, then perform factory reset
android.os.SystemClock.sleep(1000 * 60);
mDPM.wipeData(0);
}
/**
* WARNING! THIS IS PERMANENT. DOES NOT CANCEL ALARMS BECAUSE IT WIPES
* ENTIRE DEVICE.
*
* This method wipes all client data from AccessMRS. After confirmation, it
* requests factory reset.
*/
public void wipeDevice() {
// NB. we don't monitor this, we simply clear Client data. On completion
// client data will be wiped (managed by AccessMRS). On receipt of
// a broadcast to wipe data and if preference is set, then this process
// will resume and the factoryReset() method will be called.
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
prefs.edit().putBoolean(Constants.PERFORM_FACTORY_RESET, true).commit();
wipeOdkData();
}
/**
* WARNING! THIS IS PERMANENT. DOES NOT CANCEL ALARMS BECAUSE WE WAIT TO
* CONFIRM THE BROADCAST BACK FROM ACCESSMRS BEFORE CANCELLING THE ALARM.
*
* Wipes ODK Data. The alarm is only cancelled after a broadcast has been
* received from AccessMRS confirming all patient data was wiped. Otherwise,
* it will continue to send the broadcast request to wipe all client data.
*/
public void wipeOdkData() {
// we don't check this one, because we dont manage the process
// instead we wait for a broadcast to tell us to send a message
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
prefs.edit().putBoolean(Constants.PERFORM_FACTORY_RESET, false).commit();
if (Constants.DEBUG)
Log.e(TAG, "wiping client data from device");
sendSingleSMS("Wiping Client Data");
Intent i = new Intent(Constants.WIPE_DATA_SERVICE);
i.putExtra(Constants.WIPE_DATA_FROM_ADMIN_SMS_REQUEST, true);
sendBroadcast(i);
}
/**
* Resets the AdminID code for sending SMS, and sends an SMS with the new
* Admin ID to the reporting line.
*/
public void resetSmsAdminId() {
final SharedPreferences prefs = new EncryptedPreferences(this, this.getSharedPreferences(Constants.ENCRYPTED_PREFS, Context.MODE_PRIVATE));
String oldAdminId = prefs.getString(Constants.UNIQUE_DEVICE_ID, "");
String rAlphaNum = (new StringGenerator(15)).getRandomAlphaNumericString();
prefs.edit().putString(Constants.UNIQUE_DEVICE_ID, rAlphaNum).commit();
String newAdminId = prefs.getString(Constants.UNIQUE_DEVICE_ID, "");
if (!newAdminId.equals(oldAdminId))
smsAdminId(); // This will in turn call CancelAdminAlarms
else
sendSingleSMS("Unable to reset Admin ID. Alarm is still active.");
}
private void smsAdminId() {
final SharedPreferences prefs = new EncryptedPreferences(this, this.getSharedPreferences(Constants.ENCRYPTED_PREFS, Context.MODE_PRIVATE));
String adminId = prefs.getString(Constants.UNIQUE_DEVICE_ID, "");
sendSingleSMS("New AdminId =" + adminId);
cancelAdminAlarms("Admin code has now been changed.");
}
// ////////////// PASSWORD RESET //////////////
public void resetPromptPassword() {
// TODO Feature: allow admin to edit this default value in a Preference
final SharedPreferences prefs = new EncryptedPreferences(this, this.getSharedPreferences(Constants.ENCRYPTED_PREFS, Context.MODE_PRIVATE));
String defaultPwd = prefs.getString(Constants.DEFAULT_PASSWORD, "12345");
boolean success = resetPassword(defaultPwd);
showPasswordResetScreen();
if (success)
cancelAdminAlarms("Reset password to default.");
else
sendSingleSMS("Failed to reset password to default. Alarm is still active.");
}
public void resetPromptPassword(String tempPassword) {
boolean success = resetPassword(tempPassword);
showPasswordResetScreen();
if (success)
cancelAdminAlarms("Reset password to SMS request.");
else
sendSingleSMS("Failed to reset password to SMS request. Alarm is still active.");
}
/**
* Resets the password to a default string that follows the device admin
* policy, and sends an SMS confirmation to the reporting line.
*/
private boolean resetPassword(String tempPassword) {
boolean reset = false;
int pwdLength = mPolicy.getPasswordLength();
int pwdQuality = mPolicy.getPasswordQuality();
// Set to Temporary Password
mPolicy.setPasswordLength(5);
mPolicy.setPasswordQuality(2);
// confirm that this worked before canceling the alarm
android.os.SystemClock.sleep(1000 * 5);
if (mDPM.resetPassword(tempPassword, DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY))
reset = true;
mDPM.lockNow();
// Reset Policy to force pwd change (IF stronger than tempPassword)
mPolicy.setPasswordLength(pwdLength);
mPolicy.setPasswordQuality(pwdQuality);
if (reset) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
settings.edit().putBoolean(Constants.SIM_ERROR_PHONE_LOCKED, false).commit();
}
return reset;
}
public void lockPromptPassword() {
boolean success = lockRandomPassword(true);
showPasswordResetScreen();
if (success)
cancelAdminAlarms("Reset password to new random string.");
else
sendSingleSMS("Unable to reset to new random password. Alarm is still active.");
}
/**
* Locks the device with a random secure string that only the device knows.
* The only way to unlock device is through sending an sms to reset the
* password to default. Also sends an SMS to the reporting line.
*/
private boolean lockRandomPassword(boolean sendPassword) {
boolean randomLock = false;
Policy policy = new Policy(mContext);
String pwd = policy.createNewSecretPwd();
if (policy.resetPassword(pwd)) {
randomLock = true;
if (sendPassword)
sendSingleSMS("Device successfully locked with new password=" + pwd);
}
mDPM.lockNow();
if (randomLock) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
settings.edit().putBoolean(Constants.SIM_ERROR_PHONE_LOCKED, true).commit();
}
return randomLock;
}
private void showPasswordResetScreen() {
Intent i = new Intent(mContext, SetUserPassword.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(SetUserPassword.SUGGEST_RESET_PASSWORD, true);
mContext.startActivity(i);
}
// ////////////// CANCEL ALARMS //////////////
private void resetAlarm(int smsIntent, String smsMessage) {
final SharedPreferences prefs = new EncryptedPreferences(this, this.getSharedPreferences(Constants.ENCRYPTED_PREFS, Context.MODE_PRIVATE));
String smsLine = prefs.getString(Constants.SMS_REPLY_LINE, Constants.DEFAULT_SMS_REPLY_LINE);
resetAlarm(smsIntent, smsLine, smsMessage);
}
private void resetAlarm(int smsIntent, String smsLine, String smsMessage) {
mResetAlarm = true;
// kill any old alarms so only 1 active device admin process
// (all alarms should have same simple pi)
cancelAlarms(mContext);
// schedule new alarm to continue after kill or reboot
mPrefs.edit().putInt(Constants.SAVED_DEVICE_ADMIN_WORK, smsIntent).commit();
mPrefs.edit().putString(Constants.SAVED_SMS_LINE, smsLine).commit();
mPrefs.edit().putString(Constants.SAVED_SMS_MESSAGE, smsMessage).commit();
scheduleAlarms(new WakelockWorkListener(), mContext, true);
}
/**
* Cancels all Device Admin Alarms, regardless of type. Sends an SMS to the
* reporting line when complete.
*/
public void cancelAdminAlarms(String message) {
if (!mResetAlarm)
return;
// Cancel everything
cancelAlarms(mContext);
mPrefs.edit().putInt(Constants.SAVED_DEVICE_ADMIN_WORK, 0).commit();
mPrefs.edit().remove(Constants.SAVED_SMS_LINE).commit();
mPrefs.edit().remove(Constants.SAVED_SMS_MESSAGE).commit();
// confirm that this worked before asking about alarms
android.os.SystemClock.sleep(1000 * 2);
if (!isAdminAlarmActive()) {
if (message != null)
sendSingleSMS(message + " All alarms now cancelled.");
} else {
// Should never happen. Set an alarm to cancel alarms!
message = "Alarm:" + message;
resetAlarm(Constants.CANCEL_ALARMS, message);
if (Constants.DEBUG)
Log.d(TAG, "Something went wrong... alarms are not canceling");
}
}
/**
* Indicates whether there is an existing device admin alarm. There is only
* one alarm active at any given time.
*
* @return true if alarm is active
*/
public boolean isAdminAlarmActive() {
Intent i = new Intent(mContext, WakelockWorkReceiver.class);
return (PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_NO_CREATE) != null);
}
// ////////////// SEND GPS //////////////
/**
* Send an SMS with the current location (by either GPS or network) to the
* default reporting line. SMS message body is of the form: <br>
* "Time=#################### Lat=################### Lon=################# Alt=########### Acc=###"
* "
*
*/
public void sendGPSCoordinates() {
if (Constants.DEBUG)
Log.d(TAG, "sending GPS");
// taken from RMaps
final LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
final Location loc1 = lm.getLastKnownLocation("gps");
final Location loc2 = lm.getLastKnownLocation("network");
boolean boolGpsEnabled = lm.isProviderEnabled("gps");
boolean boolNetworkEnabled = lm.isProviderEnabled("network");
String str = "";
Location loc = null;
if (loc1 == null && loc2 != null)
loc = loc2;
else if (loc1 != null && loc2 == null)
loc = loc1;
else if (loc1 == null && loc2 == null)
loc = null;
else
loc = loc1.getTime() > loc2.getTime() ? loc1 : loc2;
if (boolGpsEnabled) {
} else if (boolNetworkEnabled)
str = getString(R.string.message_gpsdisabled);
else if (loc == null)
str = getString(R.string.message_locationunavailable);
else
str = getString(R.string.message_lastknownlocation);
if (str.length() > 0)
Log.d(TAG, str);
StringBuilder sb = new StringBuilder();
if (loc != null) {
sb.append("Time=");
sb.append(String.valueOf(loc.getTime()));
sb.append(" Lat=");
sb.append(String.valueOf(loc.getLatitude()));
sb.append(" Lon=");
sb.append(String.valueOf(loc.getLongitude()));
if (loc.hasAltitude()) {
sb.append(" Alt=");
sb.append(String.valueOf(loc.getAltitude()));
}
if (loc.hasAccuracy()) {
sb.append(" Acc=");
sb.append(String.valueOf(loc.getAccuracy()));
}
} else {
sb.append("No location available");
}
sendRepeatingSMS(Constants.SEND_GPS, sb.toString()); // This will in
// turn call
// CancelAdminAlarms
}
// ////////////// SEND SIM CODE //////////////
/**
* Verify that the SIM Code has in fact changed by toggling Airplane Mode on
* if necessary. If changed, log change and send the SIM. If not change,
* then cancelAlarms.
*
*/
public void verifySIMCode() {
// TODO Feature: make this into a real preference...
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
boolean useSimLock = settings.getBoolean(Constants.USE_SIM_LOCK, true);
if (!useSimLock) {
cancelAdminAlarms(null);
return;
}
int simStatus = checkSimStatus();
boolean wipeData = logSimChange(simStatus);
int count = 0;
switch (simStatus) {
case SIM_VERIFIED:
if (Constants.DEBUG)
Log.v(TAG, "SIM Verified after Airplane Mode Was Turned Off");
cancelAdminAlarms(null);
return;
case SIM_CHANGED:
count = settings.getInt(Constants.SIM_CHANGE_COUNT, 0);
if (count < 2)
holdDevice(Constants.SIM_ERROR, getString(R.string.sim_message_replace_sim), getString(R.string.sim_submessage_lock_phone), getString(R.string.return_info_message));
else
holdDevice(Constants.SIM_ERROR, getString(R.string.sim_message_replace_sim), getString(R.string.sim_submessage_attempts_change, count), getString(R.string.return_info_message));
break;
case SIM_MISSING:
default:
count = settings.getInt(Constants.SIM_MISSING_COUNT, 0);
if (count < 2)
holdDevice(Constants.SIM_ERROR, getString(R.string.sim_message_replace_sim), getString(R.string.sim_submessage_lock_phone), getString(R.string.return_info_message));
else
holdDevice(Constants.SIM_ERROR, getString(R.string.sim_message_replace_sim), getString(R.string.sim_submessage_attempts_missing, count), getString(R.string.return_info_message));
break;
}
// SIM is missing or changed!
// 1. Send Repeating SMS with SIM Code
resetAlarm(Constants.SEND_SMS, makeSIMCodeSms());
// 2. Set Alarm to lock the phone and potentially wipe data
setLockPhoneAlarm(wipeData);
}
private void setLockPhoneAlarm(boolean wipeData) {
Intent i = new Intent(mContext, LockPhoneReceiver.class);
i.putExtra(Constants.SIM_ERROR_WIPE_DATA, wipeData);
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
AlarmManager aM = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
aM.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_FIFTEEN_MINUTES / 3, pi);
}
private int checkSimStatus() {
int simStatus = 0;
// Toggle Airplane Mode if necessary
boolean enabled = Settings.System.getInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 1;
if (enabled)
turnAirplaneModeOff();
// Get Current SIM
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
String registeredSimSerial = settings.getString(Constants.SIM_SERIAL, null);
String registeredSimLine = settings.getString(Constants.SIM_LINE, null);
TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
String currentSimSerial = tm.getSimSerialNumber();
String currentSimLine = tm.getLine1Number();
if (currentSimLine == null || currentSimSerial == null || currentSimSerial.equals("")) {
if (Constants.DEBUG)
Log.w(TAG, "SIM has been taken out of phone or is not registering with device \n\t CURRENT SIM LINE: " + currentSimLine + " \n\t CURRENT SIM SERIAL: " + currentSimSerial);
simStatus = SIM_MISSING;
} else if (!currentSimLine.equals(registeredSimLine) || !currentSimSerial.equals(registeredSimSerial)) {
if (Constants.DEBUG)
Log.w(TAG, "SIM has been changed from the initial registered SIM \n\t CURRENT SIM LINE: " + currentSimLine + " DOES NOT MATCH. \n\t CURRENT SIM SERIAL: " + currentSimSerial + " DOES NOT MATCH.");
simStatus = SIM_CHANGED;
} else if (currentSimLine.equals(registeredSimLine) && currentSimSerial.equals(registeredSimSerial)) {
simStatus = SIM_VERIFIED;
}
return simStatus;
}
private boolean turnAirplaneModeOff() {
boolean enabled = Settings.System.getInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 1;
if (enabled && mAirplaneCount < 4) {
// Try to turn airplane mode off
Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0);
Intent i = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
i.putExtra("state", 0);
mContext.sendBroadcast(i);
if (Constants.DEBUG)
Log.v(TAG, "turning off airplane mode");
android.os.SystemClock.sleep(1000 * 5);
mAirplaneCount++;
turnAirplaneModeOff();
} else {
// Airplane Mode is off, or not able to turn off
mAirplaneCount = 0;
}
return enabled;
}
private boolean logSimChange(int simStatus) {
boolean wipeData = false;
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
String simCountPeriodPref = "";
String simCountPref = "";
String simThresholdPref = "";
switch (simStatus) {
case SIM_VERIFIED:
return wipeData;
case SIM_CHANGED:
simThresholdPref = Constants.SIM_CHANGE_THRESHOLD;
simCountPeriodPref = Constants.SIM_CHANGE_RESET_PERIOD;
simCountPref = Constants.SIM_CHANGE_COUNT;
break;
case SIM_MISSING:
default:
simThresholdPref = Constants.SIM_MISSING_THRESHOLD;
simCountPeriodPref = Constants.SIM_MISSING_RESET_PERIOD;
simCountPref = Constants.SIM_MISSING_COUNT;
break;
}
// Get threshold for locking phone
int simLockThreshold = settings.getInt(simThresholdPref, 7);
// Record all most recent SIM Changes in the Count Period set in Prefs
int simCountPeriod = settings.getInt(simCountPeriodPref, 7);
Long now = System.currentTimeMillis();
int days = 1000 * 60 * 60 * 24;
// Remove any previous lock that does not fall in the count period
ArrayList<Long> recentSimChange = new ArrayList<Long>();
for (int i = 0; i < simLockThreshold; i++) {
long previousLockTime = settings.getLong(simCountPref + "_" + i, 0);
if (previousLockTime > 0) {
long deltaSimChange = now - previousLockTime;
if (deltaSimChange < (simCountPeriod * days))
recentSimChange.add(previousLockTime);
}
}
// If already at lock threshold, replace oldest SIM change with current
// one
if (recentSimChange.size() == simLockThreshold)
recentSimChange.remove(Collections.min(recentSimChange));
recentSimChange.add(now);
// Save the new list of SIM changes to preferences
SharedPreferences.Editor editor = settings.edit();
for (int i = 0; i < recentSimChange.size(); i++)
editor.putLong(simCountPref + "_" + i, recentSimChange.get(i));
// Save the count of recent locks for quick reference
editor.putInt(simCountPref, recentSimChange.size());
editor.commit();
if (recentSimChange.size() >= simLockThreshold)
wipeData = true;
if (Constants.DEBUG)
Log.e(TAG, "Logging that the SIM has been changed! \n\t Current SIM Change Count=" + recentSimChange.size() + "\n\t Requires WIPE DATA=" + wipeData);
return wipeData;
}
/**
* Send an SMS with the current SIM code to the default reporting line. SMS
* message body is of the form: <br>
* "IMEI=#################### New SIM=########## Serial=##############"
*
*/
public String makeSIMCodeSms() {
TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
StringBuilder sb = new StringBuilder();
String imei = tm.getDeviceId();
String line = tm.getLine1Number();
String serial = tm.getSimSerialNumber();
if (imei != null) {
sb.append("IMEI=");
sb.append(imei);
}
if (line != null) {
sb.append(" New SIM=");
sb.append(line);
}
if (serial != null) {
sb.append(" Serial=");
sb.append(serial);
}
if (line == null && serial == null) {
sb.append("Could not obtain SIM information");
}
return sb.toString();
}
// ////////////// SEND SMS //////////////
/**
* Send an SMS associated with a wakelock alarm. Will monitor and cancel the
* alarm once the message has been sent.
*
* @param smstype
* The intent int extra that specifies the type of SMS to be sent
* (GPS coordinates, SIM code, general)
* @param smsline
* The phone number to send the SMS
* @param newmessage
* The body of the SMS (should not be longer than 160
* characters).
*/
public void sendRepeatingSMS(int smstype, String line, String message) {
String lastSentAdminMessage = mPrefs.getString(String.valueOf(smstype), "");
String outgoingSms = SendSMSService.SMS_REPLY_PREFIX + message;
if (outgoingSms.equals(lastSentAdminMessage)) {
if (Constants.DEBUG)
Log.d(TAG, "Message has already been sent. Alarm Cancelled and Prefs erased.");
cancelAdminAlarms("All SMS now confirmed as successfully sent.");
mPrefs.edit().putString(String.valueOf(smstype), "").commit();
} else {
if (Constants.DEBUG)
Log.d(TAG, "Attempting to send a repeating SMS with \n\t ID=" + smstype + "\n\t MESSAGE=" + message);
ComponentName comp = new ComponentName(mContext.getPackageName(), SendSMSService.class.getName());
Intent i = new Intent();
i.setComponent(comp);
i.putExtra(Constants.SMS_SENT_CONFIRMATION, true);
i.putExtra(Constants.DEVICE_ADMIN_WORK, smstype);
i.putExtra(Constants.SMS_LINE, line);
i.putExtra(Constants.SMS_MESSAGE, message);
mContext.startService(i);
}
}
public void sendRepeatingSMS(int smstype, String message) {
final SharedPreferences prefs = new EncryptedPreferences(this, this.getSharedPreferences(Constants.ENCRYPTED_PREFS, Context.MODE_PRIVATE));
String line = prefs.getString(Constants.SMS_REPLY_LINE, Constants.DEFAULT_SMS_REPLY_LINE);
sendRepeatingSMS(smstype, line, message);
}
/**
* Send an single SMS to the reporting line from SharedPreferences.
*
* @param message
* The body of the SMS (should not be longer than 160
* characters).
*/
public void sendSingleSMS(String message) {
final SharedPreferences prefs = new EncryptedPreferences(this, this.getSharedPreferences(Constants.ENCRYPTED_PREFS, Context.MODE_PRIVATE));
String line = prefs.getString(Constants.SMS_REPLY_LINE, Constants.DEFAULT_SMS_REPLY_LINE);
sendSingleSMS(line, message);
}
/**
* Send an single SMS with desired message and body.
*
* @param line
* The phone number to send the SMS
* @param message
* The body of the SMS (should not be longer than 160
* characters).
*/
public void sendSingleSMS(String line, String message) {
ComponentName comp = new ComponentName(mContext.getPackageName(), SendSMSService.class.getName());
Intent i = new Intent();
i.setComponent(comp);
i.putExtra(Constants.SMS_LINE, line);
i.putExtra(Constants.SMS_MESSAGE, message);
mContext.startService(i);
}
}