18 | * Our SQLlite database
19 | */
20 | public class Database extends SQLiteOpenHelper {
21 |
22 | public static String NAME = "callRecorder";
23 | public static int VERSION = 1;
24 |
25 | String CREATE_CALL_RECORDS_TABLE = "CREATE TABLE records(_id INTEGER PRIMARY KEY, phone_number TEXT, outgoing INTEGER, start_date_time INTEGER, end_date_time INTEGER, path_to_recording TEXT, keep INTEGER DEFAULT 0, backup_state INTEGER DEFAULT 0 )";
26 | public static String CALL_RECORDS_TABLE = "records";
27 | public static String CALL_RECORDS_TABLE_ID = "_id"; // only because of https://developer.android.com/reference/android/widget/CursorAdapter.html
28 | public static String CALL_RECORDS_TABLE_PHONE_NUMBER = "phone_number";
29 | public static String CALL_RECORDS_TABLE_OUTGOING = "outgoing";
30 | public static String CALL_RECORDS_TABLE_START_DATE = "start_date_time";
31 | public static String CALL_RECORDS_TABLE_END_DATE = "end_date_time";
32 | public static String CALL_RECORDS_TABLE_RECORDING_PATH = "path_to_recording";
33 | public static String CALL_RECORDS_TABLE_KEEP = "keep";
34 | public static String CALL_RECORDS_BACKUP_STATE = "backup_state";
35 |
36 | public static String CREATE_WHITELIST_TABLE = "CREATE TABLE whitelist( _id INTEGER PRIMARY KEY, contact_id TEXT, record INTEGER )";
37 | public static String WHITELIST_TABLE = "whitelist";
38 | public static String WHITELIST_TABLE_ID = "_id"; // only because of https://developer.android.com/reference/android/widget/CursorAdapter.html
39 | public static String WHITELIST_TABLE_CONTACT_ID = "contact_id";
40 | public static String WHITELIST_TABLE_RECORD = "record";
41 |
42 | private static Database instance;
43 |
44 | public static synchronized Database getInstance(Context context) {
45 | if (instance == null) {
46 | instance = new Database(context.getApplicationContext());
47 | }
48 | return instance;
49 | }
50 |
51 | private Database(Context context) {
52 | super(context, Database.NAME, null, Database.VERSION);
53 | }
54 |
55 | @Override
56 | public synchronized void onCreate(SQLiteDatabase db) {
57 | try {
58 | db.execSQL(CREATE_CALL_RECORDS_TABLE);
59 | } catch (SQLException e) {
60 | e.printStackTrace();
61 | }
62 | try {
63 | db.execSQL(CREATE_WHITELIST_TABLE);
64 | } catch (SQLException e) {
65 | e.printStackTrace();
66 | }
67 | }
68 |
69 | @Override
70 | public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
71 | if (oldVersion < 2) {
72 | }
73 | }
74 |
75 |
76 | private CallLog getCallLogFrom(Cursor cursor) {
77 | CallLog phoneCall = new CallLog();
78 | phoneCall.isNew = false;
79 |
80 | // String[] columnNames = cursor.getColumnNames();
81 |
82 | int index = cursor.getColumnIndex(CALL_RECORDS_TABLE_ID);
83 | phoneCall.getContent().put(CALL_RECORDS_TABLE_ID, cursor.getInt(index));
84 |
85 | index = cursor.getColumnIndex(CALL_RECORDS_TABLE_PHONE_NUMBER);
86 | phoneCall.getContent().put(CALL_RECORDS_TABLE_PHONE_NUMBER, cursor.getString(index));
87 |
88 | index = cursor.getColumnIndex(CALL_RECORDS_TABLE_OUTGOING);
89 | phoneCall.getContent().put(CALL_RECORDS_TABLE_OUTGOING, cursor.getInt(index));
90 |
91 | index = cursor.getColumnIndex(CALL_RECORDS_TABLE_START_DATE);
92 | phoneCall.getContent().put(CALL_RECORDS_TABLE_START_DATE, cursor.getLong(index));
93 |
94 | index = cursor.getColumnIndex(CALL_RECORDS_TABLE_END_DATE);
95 | phoneCall.getContent().put(CALL_RECORDS_TABLE_END_DATE, cursor.getLong(index));
96 |
97 | index = cursor.getColumnIndex(CALL_RECORDS_TABLE_RECORDING_PATH);
98 | phoneCall.getContent().put(CALL_RECORDS_TABLE_RECORDING_PATH, cursor.getString(index));
99 |
100 | index = cursor.getColumnIndex(CALL_RECORDS_TABLE_KEEP);
101 | phoneCall.getContent().put(CALL_RECORDS_TABLE_KEEP, cursor.getInt(index));
102 |
103 | index = cursor.getColumnIndex(CALL_RECORDS_BACKUP_STATE);
104 | phoneCall.getContent().put(CALL_RECORDS_BACKUP_STATE, cursor.getInt(index));
105 |
106 | return phoneCall;
107 | }
108 |
109 |
110 | public synchronized ArrayList getAllCalls() {
111 | ArrayList array_list = new ArrayList();
112 |
113 | SQLiteDatabase db = this.getReadableDatabase();
114 | try {
115 | Cursor cursor = db.rawQuery("select * from " + Database.CALL_RECORDS_TABLE, null);
116 | cursor.moveToFirst();
117 | while (cursor.isAfterLast() == false) {
118 | CallLog phoneCall = getCallLogFrom(cursor);
119 | array_list.add(phoneCall);
120 | cursor.moveToNext();
121 | }
122 | return array_list;
123 | } finally {
124 | db.close();
125 | }
126 | }
127 |
128 |
129 | public synchronized ArrayList getAllCalls(boolean outgoing) {
130 | ArrayList array_list = new ArrayList();
131 | SQLiteDatabase db = this.getReadableDatabase();
132 |
133 | try {
134 | Cursor cursor = db.rawQuery("select * from " + Database.CALL_RECORDS_TABLE + " where " + Database.CALL_RECORDS_TABLE_OUTGOING + "=" + (outgoing ? "1" : "0"), null);
135 | cursor.moveToFirst();
136 | while (cursor.isAfterLast() == false) {
137 | CallLog phoneCall = getCallLogFrom(cursor);
138 | array_list.add(phoneCall);
139 | cursor.moveToNext();
140 | }
141 | return array_list;
142 | } finally {
143 | db.close();
144 | }
145 | }
146 |
147 | public synchronized boolean addCall(CallLog phoneCall) {
148 | SQLiteDatabase db = this.getWritableDatabase();
149 | try {
150 | if (phoneCall.isNew) {
151 | long rowId = db.insert(Database.CALL_RECORDS_TABLE, null, phoneCall.getContent());
152 | // rowID is and Alias for _ID see: http://www.sqlite.org/autoinc.html
153 | phoneCall.getContent().put(Database.CALL_RECORDS_TABLE_ID, rowId);
154 | } else {
155 | db.update(Database.CALL_RECORDS_TABLE, phoneCall.getContent(), CALL_RECORDS_TABLE_ID + "=" + phoneCall.getId(), null);
156 | }
157 | return true;
158 | } catch (Exception e) {
159 | e.printStackTrace();
160 | return false;
161 | } finally {
162 | db.close();
163 | }
164 | }
165 |
166 | public synchronized boolean updateCall(CallLog phoneCall) {
167 | SQLiteDatabase db = this.getWritableDatabase();
168 | try {
169 | db.update(Database.CALL_RECORDS_TABLE, phoneCall.getContent(), "id = ?", new String[]{Integer.toString(phoneCall.getId())});
170 | return true;
171 | } finally {
172 | db.close();
173 | }
174 | }
175 |
176 | public synchronized int count() {
177 | SQLiteDatabase db = this.getReadableDatabase();
178 | try {
179 | int numRows = (int) DatabaseUtils.queryNumEntries(db, Database.CALL_RECORDS_TABLE);
180 | return numRows;
181 | } finally {
182 | db.close();
183 | }
184 | }
185 |
186 | public synchronized CallLog getCall(int id) {
187 | SQLiteDatabase db = this.getReadableDatabase();
188 | try {
189 | Cursor cursor = db.rawQuery("select * from " + Database.CALL_RECORDS_TABLE + " where " + Database.CALL_RECORDS_TABLE_ID + "=" + id, null);
190 | if (!cursor.moveToFirst()) return null; // does not exist
191 | return getCallLogFrom(cursor);
192 | } finally {
193 | db.close();
194 | }
195 | }
196 |
197 | public synchronized void removeCall(int id) {
198 | SQLiteDatabase db = this.getReadableDatabase();
199 | try {
200 | Cursor cursor = db.rawQuery("select * from " + Database.CALL_RECORDS_TABLE + " where " + Database.CALL_RECORDS_TABLE_ID + "=" + id, null);
201 | if (!cursor.moveToFirst()) return; // doesn't exist
202 | CallLog call = getCallLogFrom(cursor);
203 | String path = call.getPathToRecording();
204 | try {
205 | if (null != path)
206 | new File(path).delete();
207 | } catch (Exception e) {
208 |
209 | }
210 | db.execSQL("Delete from " + Database.CALL_RECORDS_TABLE + " where " + Database.CALL_RECORDS_TABLE_ID + "=" + id);
211 | } finally {
212 | db.close();
213 | }
214 | }
215 |
216 |
217 | public synchronized void removeAllCalls(boolean includeKept) {
218 | final ArrayList allCalls = getAllCalls();
219 | SQLiteDatabase db = this.getWritableDatabase();
220 | try {
221 | for (CallLog call : allCalls) {
222 | if (includeKept || !call.isKept()) {
223 | try {
224 | new File(call.getPathToRecording()).delete();
225 | } catch (Exception e) {
226 | }
227 | try {
228 | db.execSQL("Delete from " + Database.CALL_RECORDS_TABLE + " where " + Database.CALL_RECORDS_TABLE_ID + "=" + call.getId());
229 | } catch (Exception e) {
230 | }
231 | }
232 | }
233 | // db.delete(Database.CALL_RECORDS_TABLE, null, null);
234 | } finally {
235 | db.close();
236 | }
237 | }
238 |
239 |
240 | private synchronized Whitelist getWhitelistFrom(Cursor cursor) {
241 | Whitelist whitelist = new Whitelist();
242 |
243 | int index = cursor.getColumnIndex(Database.WHITELIST_TABLE_ID);
244 | whitelist.getContent().put(Database.WHITELIST_TABLE_ID, cursor.getInt(index));
245 |
246 | index = cursor.getColumnIndex(Database.WHITELIST_TABLE_CONTACT_ID);
247 | whitelist.getContent().put(Database.WHITELIST_TABLE_CONTACT_ID, cursor.getString(index));
248 |
249 | index = cursor.getColumnIndex(Database.WHITELIST_TABLE_RECORD);
250 | whitelist.getContent().put(Database.WHITELIST_TABLE_RECORD, cursor.getString(index));
251 |
252 | return whitelist;
253 | }
254 |
255 |
256 | public synchronized ArrayList getAllWhitelist() {
257 | ArrayList array_list = new ArrayList();
258 |
259 | //hp = new HashMap();
260 | SQLiteDatabase db = this.getReadableDatabase();
261 | try {
262 | Cursor cursor = db.rawQuery("select * from " + Database.WHITELIST_TABLE, null);
263 | String[] columns = cursor.getColumnNames();
264 |
265 | cursor.moveToFirst();
266 | while (cursor.isAfterLast() == false) {
267 | Whitelist contact = getWhitelistFrom(cursor);
268 | array_list.add(contact);
269 | cursor.moveToNext();
270 | }
271 | return array_list;
272 | } finally {
273 | db.close();
274 | }
275 | }
276 |
277 | public synchronized Whitelist getContact(String contactId) {
278 | SQLiteDatabase db = this.getReadableDatabase();
279 | try {
280 | Cursor cursor = db.rawQuery("select * from " + Database.WHITELIST_TABLE + " where " + Database.WHITELIST_TABLE_CONTACT_ID + " = " + contactId + "", null);
281 | if (!cursor.moveToFirst()) return null; // does not exist
282 | return getWhitelistFrom(cursor);
283 | } finally {
284 | db.close();
285 | }
286 | }
287 |
288 |
289 | public synchronized boolean addWhitelist(Whitelist contact) {
290 | SQLiteDatabase db = this.getWritableDatabase();
291 | try {
292 | db.insert(Database.WHITELIST_TABLE, null, contact.getContent());
293 | return true;
294 | } finally {
295 | db.close();
296 | }
297 | }
298 |
299 |
300 | public synchronized void removeWhiteList(int id) {
301 | SQLiteDatabase db = this.getReadableDatabase();
302 | try {
303 | db.execSQL("Delete from " + Database.WHITELIST_TABLE + " where " + Database.WHITELIST_TABLE_ID + "=" + id + "");
304 | } finally {
305 | db.close();
306 | }
307 | }
308 |
309 | public synchronized static boolean isWhitelisted(Context context, String phoneNumber) {
310 | // define the columns the query should return
311 | String[] projection = new String[]{ContactsContract.PhoneLookup._ID};
312 | // encode the phone number and build the filter URI
313 | Uri contactUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
314 | // query time
315 | Cursor cursor = context.getContentResolver().query(contactUri, projection, null, null, null);
316 |
317 | if (cursor.moveToFirst()) {
318 | String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID));
319 | // Found the callers... now check the whitelist
320 | Whitelist whitelist = getInstance(context).getContact(contactId);
321 | return (null != whitelist);
322 | }
323 | return false;
324 | }
325 |
326 |
327 | }
328 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jlcsoftware/database/Whitelist.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.database;
2 |
3 | import android.content.ContentValues;
4 | import android.database.Cursor;
5 |
6 | /**
7 | * Created by Jeff on 07-May-16.
8 | *
9 | * A Whitelist record from our database
10 | */
11 | public class Whitelist {
12 |
13 | boolean isNew = true;
14 |
15 | private ContentValues content;
16 |
17 | public Whitelist(){
18 | content = new ContentValues();
19 | content.put(Database.WHITELIST_TABLE_RECORD, false); // default: do NOT record
20 | }
21 |
22 | public String getContactId(){
23 | return content.getAsString(Database.WHITELIST_TABLE_CONTACT_ID);
24 | }
25 | public void setContactId(String contactId){
26 | content.put(Database.WHITELIST_TABLE_CONTACT_ID,contactId);
27 | }
28 |
29 | public boolean isRecordable(){
30 | return content.getAsBoolean(Database.WHITELIST_TABLE_RECORD);
31 | }
32 |
33 | public void setRecordable(boolean enable){
34 | content.put(Database.WHITELIST_TABLE_RECORD,enable);
35 | }
36 |
37 | public int getId(){
38 | return content.getAsInteger(Database.WHITELIST_TABLE_ID);
39 | }
40 |
41 |
42 | public ContentValues getContent(){
43 | return content;
44 | }
45 |
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jlcsoftware/listeners/PhoneListener.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.listeners;
2 |
3 | import android.content.Context;
4 | import android.telephony.PhoneStateListener;
5 | import android.telephony.TelephonyManager;
6 |
7 | import com.jlcsoftware.database.CallLog;
8 | import com.jlcsoftware.database.Database;
9 | import com.jlcsoftware.receivers.MyCallReceiver;
10 | import com.jlcsoftware.services.RecordCallService;
11 |
12 | import java.util.concurrent.atomic.AtomicBoolean;
13 |
14 | /**
15 | * Created by Jeff on 01-May-16.
16 | *
17 | * The logic is a little odd here...
18 | *
19 | * When a incoming call comes in, we get a CALL_STATE_RINGING that provides the incoming number and all is easy and good...
20 | * on the other hand, a Outgoing call generates a ACTION_NEW_OUTGOING_CALL with the phone number, then an a CALL_STATE_IDLE and then a
21 | * CALL_STATE_OFFHOOK when the call connects - we never get the outgoing number in the PhoneState Change
22 | *
23 | */
24 | public class PhoneListener extends PhoneStateListener {
25 |
26 | private static PhoneListener instance = null;
27 |
28 | /**
29 | * Must be called once on app startup
30 | *
31 | * @param context - application context
32 | * @return
33 | */
34 | public static PhoneListener getInstance(Context context) {
35 | if (instance == null) {
36 | instance = new PhoneListener(context);
37 | }
38 | return instance;
39 | }
40 |
41 | public static boolean hasInstance() {
42 | return null != instance;
43 | }
44 |
45 | private final Context context;
46 | private CallLog phoneCall;
47 |
48 | private PhoneListener(Context context) {
49 | this.context = context;
50 | }
51 |
52 | AtomicBoolean isRecording = new AtomicBoolean();
53 | AtomicBoolean isWhitelisted = new AtomicBoolean();
54 |
55 |
56 | /**
57 | * Set the outgoing phone number
58 | *
59 | * Called by {@link MyCallReceiver} since that is where the phone number is available in a outgoing call
60 | *
61 | * @param phoneNumber
62 | */
63 | public void setOutgoing(String phoneNumber) {
64 | if (null == phoneCall)
65 | phoneCall = new CallLog();
66 | phoneCall.setPhoneNumber(phoneNumber);
67 | phoneCall.setOutgoing();
68 | // called here so as not to miss recording part of the conversation in TelephonyManager.CALL_STATE_OFFHOOK
69 | isWhitelisted.set(Database.isWhitelisted(context, phoneCall.getPhoneNumber()));
70 | }
71 |
72 | @Override
73 | public void onCallStateChanged(int state, String incomingNumber) {
74 | super.onCallStateChanged(state, incomingNumber);
75 |
76 | switch (state) {
77 | case TelephonyManager.CALL_STATE_IDLE: // Idle... no call
78 | if (isRecording.get()) {
79 | RecordCallService.stopRecording(context);
80 | phoneCall = null;
81 | isRecording.set(false);
82 | }
83 | break;
84 | case TelephonyManager.CALL_STATE_OFFHOOK: // Call answered
85 | if (isWhitelisted.get()) {
86 | isWhitelisted.set(false);
87 | return;
88 | }
89 | if (!isRecording.get()) {
90 | isRecording.set(true);
91 | // start: Probably not ever usefull
92 | if (null == phoneCall)
93 | phoneCall = new CallLog();
94 | if (!incomingNumber.isEmpty()) {
95 | phoneCall.setPhoneNumber(incomingNumber);
96 | }
97 | // end: Probably not ever usefull
98 | RecordCallService.sartRecording(context, phoneCall);
99 | }
100 | break;
101 | case TelephonyManager.CALL_STATE_RINGING: // Phone ringing
102 | // DO NOT try RECORDING here! Leads to VERY poor quality recordings
103 | // I think something is not fully settled with the Incoming phone call when we get CALL_STATE_RINGING
104 | // a "SystemClock.sleep(1000);" in the code will allow the incoming call to stabilize and produce a good recording...(as proof of above)
105 | if (null == phoneCall)
106 | phoneCall = new CallLog();
107 | if (!incomingNumber.isEmpty()) {
108 | phoneCall.setPhoneNumber(incomingNumber);
109 | // called here so as not to miss recording part of the conversation in TelephonyManager.CALL_STATE_OFFHOOK
110 | isWhitelisted.set(Database.isWhitelisted(context, phoneCall.getPhoneNumber()));
111 | }
112 | break;
113 | }
114 |
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jlcsoftware/receivers/MyAlarmReceiver.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.receivers;
2 |
3 | import android.app.AlarmManager;
4 | import android.app.PendingIntent;
5 | import android.content.BroadcastReceiver;
6 | import android.content.Context;
7 | import android.content.Intent;
8 |
9 | import com.jlcsoftware.services.CleanupService;
10 |
11 | /**
12 | * Handles the cleaning of old recordings, on a schedule
13 | */
14 |
15 | public class MyAlarmReceiver extends BroadcastReceiver {
16 | public MyAlarmReceiver() {
17 | }
18 |
19 | @Override
20 | public void onReceive(Context context, Intent intent) {
21 | CleanupService.sartCleaning(context);
22 | }
23 |
24 | /**
25 | * set the system "Alarm"
26 | * @param context
27 | */
28 |
29 | public static void setAlarm(Context context) {
30 | AlarmManager alarmMgr;
31 | PendingIntent alarmIntent;
32 |
33 | alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
34 | Intent intent = new Intent(context, MyAlarmReceiver.class);
35 | alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
36 | alarmMgr.cancel(alarmIntent);
37 | /*
38 | // Debug - Every 30 seconds
39 | alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
40 | 30 * 1000,
41 | 30 * 1000, alarmIntent);
42 | */
43 |
44 | alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
45 | AlarmManager.INTERVAL_DAY,
46 | AlarmManager.INTERVAL_DAY, alarmIntent);
47 | }
48 |
49 | /**
50 | * cancel the system "Alarm"
51 | * @param context
52 | */
53 | public static void cancleAlarm(Context context) {
54 | AlarmManager alarmMgr;
55 | PendingIntent alarmIntent;
56 | alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
57 | Intent intent = new Intent(context, MyAlarmReceiver.class);
58 | alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
59 | alarmMgr.cancel(alarmIntent);
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jlcsoftware/receivers/MyCallReceiver.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.receivers;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.telephony.PhoneStateListener;
7 | import android.telephony.TelephonyManager;
8 | import android.util.Log;
9 |
10 | import com.jlcsoftware.callrecorder.AppPreferences;
11 | import com.jlcsoftware.listeners.PhoneListener;
12 |
13 |
14 | /**
15 | * Handle the Phone call related BroadcastActions
16 | *
17 | *
18 | */
19 |
20 | public class MyCallReceiver extends BroadcastReceiver {
21 |
22 | public MyCallReceiver() {
23 | }
24 |
25 | static TelephonyManager manager;
26 |
27 | @Override
28 | public void onReceive(Context context, Intent intent) {
29 | Log.i("JLCreativeCallRecorder", "MyCallReceiver.onReceive ");
30 |
31 | if (!AppPreferences.getInstance(context).isRecordingEnabled()) {
32 | removeListener();
33 | return;
34 | }
35 |
36 | if (Intent.ACTION_NEW_OUTGOING_CALL.equals(intent.getAction())) {
37 | if (!AppPreferences.getInstance(context).isRecordingOutgoingEnabled()) {
38 | removeListener();
39 | return;
40 | }
41 | PhoneListener.getInstance(context).setOutgoing(intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
42 | } else {
43 | if (!AppPreferences.getInstance(context).isRecordingIncomingEnabled()) {
44 | removeListener();
45 | return;
46 | }
47 | }
48 |
49 | // Start Listening to the call....
50 | if (null == manager) {
51 | manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
52 | }
53 | if (null != manager)
54 | manager.listen(PhoneListener.getInstance(context), PhoneStateListener.LISTEN_CALL_STATE);
55 | }
56 |
57 | private void removeListener() {
58 | if (null != manager) {
59 | if (PhoneListener.hasInstance())
60 | manager.listen(PhoneListener.getInstance(null), PhoneStateListener.LISTEN_NONE);
61 | }
62 | }
63 |
64 | }
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jlcsoftware/receivers/MyLocalBroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.receivers;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 |
7 | import com.jlcsoftware.callrecorder.LocalBroadcastActions;
8 |
9 | /**
10 | * Internal BroadcastReceiver to handle
11 | * {@link LocalBroadcastActions}
12 | */
13 |
14 | public class MyLocalBroadcastReceiver extends BroadcastReceiver {
15 |
16 | public interface OnNewRecordingListener{
17 | void OnBroadcastReceived(Intent intent);
18 | }
19 |
20 | OnNewRecordingListener listener;
21 |
22 | public MyLocalBroadcastReceiver(OnNewRecordingListener listener) {
23 | this.listener = listener;
24 | }
25 |
26 | public MyLocalBroadcastReceiver() {
27 | }
28 |
29 | public void setListener( OnNewRecordingListener listener){
30 | this.listener = listener;
31 | }
32 |
33 | @Override
34 | public void onReceive(Context context, Intent intent) {
35 | if(null!=listener) listener.OnBroadcastReceived(intent);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jlcsoftware/services/CleanupService.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.services;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.IBinder;
7 | import android.support.v4.content.LocalBroadcastManager;
8 |
9 | import com.jlcsoftware.callrecorder.AppPreferences;
10 | import com.jlcsoftware.callrecorder.LocalBroadcastActions;
11 | import com.jlcsoftware.database.CallLog;
12 | import com.jlcsoftware.database.Database;
13 |
14 | import java.util.ArrayList;
15 | import java.util.Calendar;
16 | import java.util.GregorianCalendar;
17 | import java.util.concurrent.TimeUnit;
18 | import java.util.concurrent.atomic.AtomicBoolean;
19 |
20 | /**
21 | * Clean up disk usage
22 | */
23 | public class CleanupService extends Service {
24 | public CleanupService() {
25 |
26 | }
27 |
28 | @Override
29 | public IBinder onBind(Intent intent) {
30 | return null; // not supported
31 | }
32 |
33 |
34 | AtomicBoolean isRunning = new AtomicBoolean();
35 |
36 | @Override
37 | public int onStartCommand(Intent intent, int flags, int startId) {
38 | super.onStartCommand(intent, flags, startId);
39 | if (!isRunning.get()) {
40 | isRunning.set(true);
41 |
42 | Runnable runnable = new Runnable() {
43 | @Override
44 | public void run() {
45 | try {
46 | AppPreferences instance = AppPreferences.getInstance(getApplicationContext());
47 | AppPreferences.OlderThan olderThan = instance.getOlderThan();
48 | if (olderThan != AppPreferences.OlderThan.NEVER) {
49 | int age = 0;
50 | switch (olderThan) {
51 | case DAILY:
52 | age = 1;
53 | break;
54 | case THREE_DAYS:
55 | age = 3;
56 | break;
57 | case WEEKLY:
58 | age = 7;
59 | break;
60 | case MONTHLY:
61 | age = 31;
62 | break;
63 | case QUARTERLY:
64 | age = 92;
65 | break;
66 | case YEARLY:
67 | age = 365;
68 | break;
69 | }
70 | boolean deleted = false;
71 | Calendar now = GregorianCalendar.getInstance();
72 | Database callLogDB = Database.getInstance(getApplicationContext());
73 | ArrayList allCalls = callLogDB.getAllCalls();
74 | for (CallLog call : allCalls) {
75 | if (!call.isKept()) {
76 | if (daysBetween(call.getEndTime(), now) >= age) {
77 | callLogDB.removeCall(call.getId());
78 | deleted = true;
79 | }
80 | }
81 | }
82 | if(deleted) LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(LocalBroadcastActions.RECORDING_DELETED_BROADCAST));
83 | }
84 | // TODO: implement cleanup via disk usage...
85 | }catch (Exception e){
86 | e.printStackTrace();
87 | }finally {
88 | isRunning.set(false);
89 | }
90 | }
91 | };
92 | new Thread(runnable).start();
93 | }
94 | return Service.START_REDELIVER_INTENT;
95 | }
96 |
97 | @Override
98 | public void onDestroy() {
99 | super.onDestroy();
100 | }
101 |
102 | public final static String ACTION_CLEAN_UP = "com.jlcsoftware.ACTION_CLEAN_UP";
103 |
104 |
105 | public static void sartCleaning(Context context) {
106 | Intent intent = new Intent(context, CleanupService.class);
107 | intent.setAction(ACTION_CLEAN_UP);
108 | context.startService(intent);
109 | }
110 |
111 |
112 | private long daysBetween(Calendar then, Calendar now) {
113 | long diff = now.getTimeInMillis() - then.getTimeInMillis();
114 | return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jlcsoftware/services/RecordCallService.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.services;
2 |
3 | import android.app.NotificationManager;
4 | import android.app.PendingIntent;
5 | import android.app.Service;
6 | import android.content.ContentValues;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.media.MediaRecorder;
10 | import android.os.IBinder;
11 | import android.support.v4.content.LocalBroadcastManager;
12 | import android.support.v7.app.NotificationCompat;
13 |
14 | import com.jlcsoftware.callrecorder.AppPreferences;
15 | import com.jlcsoftware.callrecorder.LocalBroadcastActions;
16 | import com.jlcsoftware.callrecorder.MainActivity;
17 | import com.jlcsoftware.callrecorder.R;
18 | import com.jlcsoftware.database.CallLog;
19 |
20 | import java.io.File;
21 | import java.util.Calendar;
22 |
23 | /**
24 | * The nitty gritty Service that handles actually recording the conversations
25 | */
26 |
27 | public class RecordCallService extends Service {
28 |
29 | public final static String ACTION_START_RECORDING = "com.jlcsoftware.ACTION_CLEAN_UP";
30 | public final static String ACTION_STOP_RECORDING = "com.jlcsoftware.ACTION_STOP_RECORDING";
31 | public final static String EXTRA_PHONE_CALL = "com.jlcsoftware.EXTRA_PHONE_CALL";
32 |
33 | public RecordCallService(){
34 | }
35 |
36 | @Override
37 | public IBinder onBind(Intent intent) {
38 | return null;
39 | }
40 |
41 |
42 | @Override
43 | public int onStartCommand(Intent intent, int flags, int startId) {
44 | ContentValues parcelableExtra = intent.getParcelableExtra(EXTRA_PHONE_CALL);
45 | startRecording(new CallLog(parcelableExtra));
46 | return START_NOT_STICKY ;
47 | }
48 |
49 | @Override
50 | public void onDestroy() {
51 | stopRecording();
52 | super.onDestroy();
53 | }
54 |
55 | private CallLog phoneCall;
56 |
57 | boolean isRecording = false;
58 |
59 | private void stopRecording() {
60 |
61 | if (isRecording) {
62 | try {
63 | phoneCall.setEndTime(Calendar.getInstance());
64 | mediaRecorder.stop();
65 | mediaRecorder.reset();
66 | mediaRecorder.release();
67 | mediaRecorder = null;
68 | isRecording = false;
69 |
70 | phoneCall.save(getBaseContext());
71 | displayNotification(phoneCall);
72 |
73 | LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(LocalBroadcastActions.NEW_RECORDING_BROADCAST));
74 | } catch (Exception e) {
75 | e.printStackTrace();
76 | }
77 | }
78 | phoneCall = null;
79 | }
80 |
81 |
82 | MediaRecorder mediaRecorder;
83 |
84 |
85 | private void startRecording(CallLog phoneCall) {
86 | if (!isRecording) {
87 | isRecording = true;
88 | this.phoneCall = phoneCall;
89 | File file = null;
90 | try {
91 | this.phoneCall.setSartTime(Calendar.getInstance());
92 | File dir = AppPreferences.getInstance(getApplicationContext()).getFilesDirectory();
93 | mediaRecorder = new MediaRecorder();
94 | file = File.createTempFile("record", ".3gp", dir);
95 | this.phoneCall.setPathToRecording(file.getAbsolutePath());
96 | mediaRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);
97 | mediaRecorder.setAudioSamplingRate(8000);
98 | mediaRecorder.setAudioEncodingBitRate(12200);
99 | mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
100 | mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
101 | mediaRecorder.setOutputFile(phoneCall.getPathToRecording());
102 | mediaRecorder.prepare();
103 | mediaRecorder.start();
104 | } catch (Exception e) {
105 | e.printStackTrace();
106 | isRecording = false;
107 | if (file != null) file.delete();
108 | this.phoneCall = null;
109 | isRecording = false;
110 | }
111 | }
112 | }
113 |
114 | public void displayNotification(CallLog phoneCall) {
115 | NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
116 |
117 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
118 | builder.setSmallIcon(R.drawable.ic_recording_conversation_white_24);
119 | builder.setContentTitle(getApplicationContext().getString(R.string.notification_title));
120 | builder.setContentText(getApplicationContext().getString(R.string.notification_text));
121 | builder.setContentInfo(getApplicationContext().getString(R.string.notification_more_text));
122 | builder.setAutoCancel(true);
123 |
124 | Intent intent = new Intent(getApplicationContext(), MainActivity.class);
125 | intent.setAction(Long.toString(System.currentTimeMillis())); // fake action to force PendingIntent.FLAG_UPDATE_CURRENT
126 | intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
127 |
128 | intent.putExtra("RecordingId", phoneCall.getId());
129 |
130 | builder.setContentIntent(PendingIntent.getActivity(this, 0xFeed, intent, PendingIntent.FLAG_UPDATE_CURRENT));
131 | notificationManager.notify(0xfeed, builder.build());
132 | }
133 |
134 |
135 | public static void sartRecording(Context context, CallLog phoneCall) {
136 | Intent intent = new Intent(context, RecordCallService.class);
137 | intent.setAction(ACTION_START_RECORDING);
138 | intent.putExtra(EXTRA_PHONE_CALL, phoneCall.getContent());
139 | context.startService(intent);
140 | }
141 |
142 |
143 | public static void stopRecording(Context context) {
144 | Intent intent = new Intent(context, RecordCallService.class);
145 | intent.setAction(ACTION_STOP_RECORDING);
146 | context.stopService(intent);
147 | }
148 |
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jlcsoftware/widget/adapter/SectionedRecyclerViewAdapter.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.widget.adapter;
2 |
3 | /**
4 | * Created by Jeff on 19-May-16.
5 | *
6 | *
7 | * Original: https://github.com/afollestad/sectioned-recyclerview
8 | *
9 | * Any modifications are mine
10 | *
11 | *
12 | * Usage:
13 | * Layout Manager
14 | * If you're using a LinearLayoutManager, you're all set. If you're using a GridLayoutManager, you need to tell the adapter:
15 | * GridLayoutManager manager = // ...
16 | * adapter.setLayoutManager(manager);
17 | * This is vital to getting headers to span all columns.
18 | *
19 | *
20 | */
21 |
22 | import android.support.annotation.IntRange;
23 | import android.support.annotation.Nullable;
24 | import android.support.v4.util.ArrayMap;
25 | import android.support.v7.widget.GridLayoutManager;
26 | import android.support.v7.widget.RecyclerView;
27 | import android.support.v7.widget.StaggeredGridLayoutManager;
28 | import android.view.ViewGroup;
29 |
30 | import java.util.List;
31 |
32 | /**
33 | * @author Aidan Follestad (afollestad)
34 | */
35 | public abstract class SectionedRecyclerViewAdapter extends RecyclerView.Adapter {
36 |
37 | protected final static int VIEW_TYPE_HEADER = -2;
38 | protected final static int VIEW_TYPE_ITEM = -1;
39 |
40 | private final ArrayMap mHeaderLocationMap;
41 | private GridLayoutManager mLayoutManager;
42 | private ArrayMap mSpanMap;
43 | private boolean mShowHeadersForEmptySections;
44 |
45 | public SectionedRecyclerViewAdapter() {
46 | mHeaderLocationMap = new ArrayMap<>();
47 | }
48 |
49 | public abstract int getSectionCount();
50 |
51 | public abstract int getItemCount(int section);
52 |
53 | public abstract void onBindHeaderViewHolder(VH holder, int section);
54 |
55 | /**
56 | * Setup non-header view.
57 | * @param holder
58 | * @param section is section index.
59 | * @param relativePosition is index in this section.
60 | * @param absolutePosition is index out of all non-header items.
61 | */
62 |
63 | public abstract void onBindViewHolder(VH holder, int section, int relativePosition, int absolutePosition);
64 |
65 | public final boolean isHeader(int position) {
66 | return mHeaderLocationMap.get(position) != null;
67 | }
68 |
69 | /**
70 | * Instructs the list view adapter to whether show headers for empty sections or not.
71 | *
72 | * @param show flag indicating whether headers for empty sections ought to be shown.
73 | */
74 | public final void shouldShowHeadersForEmptySections(boolean show) {
75 | mShowHeadersForEmptySections = show;
76 | }
77 |
78 | public final void setLayoutManager(@Nullable GridLayoutManager lm) {
79 | mLayoutManager = lm;
80 | if (lm == null) return;
81 | lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
82 | @Override
83 | public int getSpanSize(int position) {
84 | if (isHeader(position))
85 | return mLayoutManager.getSpanCount();
86 | final int[] sectionAndPos = getSectionIndexAndRelativePosition(position);
87 | final int absPos = position - (sectionAndPos[0] + 1);
88 | return getRowSpan(mLayoutManager.getSpanCount(),
89 | sectionAndPos[0], sectionAndPos[1], absPos);
90 | }
91 | });
92 | }
93 |
94 | @SuppressWarnings("UnusedParameters")
95 | protected int getRowSpan(int fullSpanSize, int section, int relativePosition, int absolutePosition) {
96 | return 1;
97 | }
98 |
99 | // returns section along with offsetted position
100 | private int[] getSectionIndexAndRelativePosition(int itemPosition) {
101 | synchronized (mHeaderLocationMap) {
102 | Integer lastSectionIndex = -1;
103 | for (final Integer sectionIndex : mHeaderLocationMap.keySet()) {
104 | if (itemPosition > sectionIndex) {
105 | lastSectionIndex = sectionIndex;
106 | } else {
107 | break;
108 | }
109 | }
110 | return new int[]{mHeaderLocationMap.get(lastSectionIndex), itemPosition - lastSectionIndex - 1};
111 | }
112 | }
113 |
114 | @Override
115 | public final int getItemCount() {
116 | int count = 0;
117 | mHeaderLocationMap.clear();
118 | for (int s = 0; s < getSectionCount(); s++) {
119 | int itemCount = getItemCount(s);
120 | if (mShowHeadersForEmptySections || (itemCount > 0)) {
121 | mHeaderLocationMap.put(count, s);
122 | count += itemCount + 1;
123 | }
124 | }
125 | return count;
126 | }
127 |
128 | /**
129 | * @hide
130 | * @deprecated
131 | */
132 | @Override
133 | @Deprecated
134 | public final int getItemViewType(int position) {
135 | if (isHeader(position)) {
136 | return getHeaderViewType(mHeaderLocationMap.get(position));
137 | } else {
138 | final int[] sectionAndPos = getSectionIndexAndRelativePosition(position);
139 | return getItemViewType(sectionAndPos[0],
140 | // offset section view positions
141 | sectionAndPos[1],
142 | position - (sectionAndPos[0] + 1));
143 | }
144 | }
145 |
146 | @SuppressWarnings("UnusedParameters")
147 | @IntRange(from = 0, to = Integer.MAX_VALUE)
148 | public int getHeaderViewType(int section) {
149 | //noinspection ResourceType
150 | return VIEW_TYPE_HEADER;
151 | }
152 |
153 | @SuppressWarnings("UnusedParameters")
154 | @IntRange(from = 0, to = Integer.MAX_VALUE)
155 | public int getItemViewType(int section, int relativePosition, int absolutePosition) {
156 | //noinspection ResourceType
157 | return VIEW_TYPE_ITEM;
158 | }
159 |
160 | /**
161 | * @hide
162 | * @deprecated
163 | */
164 | @Override
165 | @Deprecated
166 | public final void onBindViewHolder(VH holder, int position) {
167 | StaggeredGridLayoutManager.LayoutParams layoutParams = null;
168 | if (holder.itemView.getLayoutParams() instanceof GridLayoutManager.LayoutParams)
169 | layoutParams = new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
170 | else if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams)
171 | layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
172 | if (isHeader(position)) {
173 | if (layoutParams != null) layoutParams.setFullSpan(true);
174 | onBindHeaderViewHolder(holder, mHeaderLocationMap.get(position));
175 | } else {
176 | if (layoutParams != null) layoutParams.setFullSpan(false);
177 | final int[] sectionAndPos = getSectionIndexAndRelativePosition(position);
178 | final int absPos = position - (sectionAndPos[0] + 1);
179 | onBindViewHolder(holder, sectionAndPos[0],
180 | // offset section view positions
181 | sectionAndPos[1], absPos);
182 | }
183 | if (layoutParams != null)
184 | holder.itemView.setLayoutParams(layoutParams);
185 | }
186 |
187 | /**
188 | * @hide
189 | * @deprecated
190 | */
191 | @Deprecated
192 | @Override
193 | public final void onBindViewHolder(VH holder, int position, List
]]>
43 | Settings
44 | Exclude List
45 | Add Contact
46 | Hello World from section: %1$d
47 |
48 | "Press BACK once more to exit"
49 | Incoming
50 | Outgoing
51 | All
52 |
53 | Delete Recording
54 | Are you sure you want to delete the recording(s)?
55 | Remove Contact
56 | Are you sure you want to remove this excluded contact(s)?
57 | Are you sure you want to delete ALL the recordings?
58 | Yes
59 | No
60 | Ok
61 | Cancel
62 |
63 | Missing Permission
64 | The Exlcude List requires permission to read your contacts. Please grant "Read Contacts" permission to this app.
65 | Off
66 | On
67 | Settings
68 |
69 | Call Recorder needs access to your contacts to identify your call recordings and prevent selected contacts from being recorded.
70 |
71 | Record Incoming Calls
72 | Record Outgoing Calls
73 | Storage
74 | Storage:
75 | Folder: \"calls\"
76 | %d]]>
77 | %d KB]]>
78 | %1.2f MB]]>
79 | Automatic Cleaning
80 | Delete Recordings:
81 | Clean Now
82 | Recording
83 |
84 |
85 | Never
86 | Daily
87 | Every 3 days
88 | Weekly
89 | Monthly
90 | Quarterly
91 |
92 |
93 | New Recording
94 | A Phone Call has been Recorded.
95 | Select to play
96 |
97 | Remove Contact
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
53 |
56 |
57 |
60 |
61 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/test/java/com/jlcsoftware/callrecorder/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.callrecorder;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rjeffm/CallRecorder/97a56c1da3d1ad7dbcbb023e47e377efcde2661a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/helpers/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/helpers/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 9
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | testCompile 'junit:junit:4.12'
24 | compile 'com.android.support:appcompat-v7:23.1.1'
25 | }
26 |
--------------------------------------------------------------------------------
/helpers/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in d:\DevTools\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/helpers/src/androidTest/java/com/jlcsoftware/helpers/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.helpers;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 |
13 | }
14 |
15 | public void testRateMe() throws Exception {
16 | //RateMeNowDialog.showRateDialog(getContext());
17 | }
18 | }
--------------------------------------------------------------------------------
/helpers/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/helpers/src/main/java/com/jlcsoftware/helpers/AboutDialog.java:
--------------------------------------------------------------------------------
1 | package com.jlcsoftware.helpers;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.text.method.LinkMovementMethod;
7 | import android.view.ContextThemeWrapper;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.View.OnClickListener;
11 | import android.widget.TextView;
12 |
13 | import java.util.concurrent.atomic.AtomicInteger;
14 |
15 |
16 | /**
17 | *
About Dialog Customization
18 | *
19 | * OVERRIDE the following string resources in the target app to customize and internationalize the about dialog.
20 | *
21 | *
22 | *
about_title - Title for the About dialog
23 | *
about_app - Information about the App, including links to our web site etc...
24 | *
about_version - The string "Version: " which will be concatenated with the app's android:versionName from the manifest
25 | *
about_model - optional - if this string has content, then it will be concatenated with uppercased android.os.Build.MANUFACTURER + "-" + android.os.Build.MODEL
26 | *
about_deviceid - optional - if this string has content, then it will be concatenated with "android-" + androidDeviceId