diff options
-rw-r--r-- | AndroidManifest.xml | 6 | ||||
-rw-r--r-- | res/values/cm_strings.xml | 5 | ||||
-rw-r--r-- | res/xml/location_settings.xml | 7 | ||||
-rw-r--r-- | src/com/android/settings/cyanogenmod/BootReceiver.java | 2 | ||||
-rw-r--r-- | src/com/android/settings/cyanogenmod/LtoService.java | 337 | ||||
-rw-r--r-- | src/com/android/settings/location/LocationSettings.java | 96 |
6 files changed, 452 insertions, 1 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dd86301..ef7c4a5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2694,5 +2694,11 @@ </intent-filter> </receiver> + <service android:label="LtoService" + android:enabled="true" + android:exported="false" + android:name=".cyanogenmod.LtoService"> + </service> + </application> </manifest> diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml index aaa1ccc..104ec13 100644 --- a/res/values/cm_strings.xml +++ b/res/values/cm_strings.xml @@ -548,4 +548,9 @@ <string name="increasing_ring_volume_option_title">Increasing ring volume</string> <string name="increasing_ring_min_volume_title">Start volume</string> <string name="increasing_ring_ramp_up_time_title">Ramp-up time</string> + + <!-- LTO download data over wi-fi only --> + <string name="lto_download_data_wifi_only">GPS assisted data download</string> + <string name="lto_download_data_wifi_only_on">Over any networks</string> + <string name="lto_download_data_wifi_only_off">Only over Wi\u2011Fi networks</string> </resources> diff --git a/res/xml/location_settings.xml b/res/xml/location_settings.xml index 2cd86f5..3b8a7c3 100644 --- a/res/xml/location_settings.xml +++ b/res/xml/location_settings.xml @@ -26,6 +26,13 @@ settings:keywords="@string/keywords_location_mode" android:summary="@string/location_mode_location_off_title" /> + <SwitchPreference + android:key="lto_download_data_wifi_only" + android:title="@string/lto_download_data_wifi_only" + android:summaryOn="@string/lto_download_data_wifi_only_on" + android:summaryOff="@string/lto_download_data_wifi_only_off" + android:defaultValue="false" /> + <!-- This preference category gets removed if there is no managed profile --> <PreferenceCategory android:key="managed_profile_location_category" diff --git a/src/com/android/settings/cyanogenmod/BootReceiver.java b/src/com/android/settings/cyanogenmod/BootReceiver.java index 4d56e60..d1a16ea 100644 --- a/src/com/android/settings/cyanogenmod/BootReceiver.java +++ b/src/com/android/settings/cyanogenmod/BootReceiver.java @@ -26,6 +26,7 @@ import com.android.settings.Utils; import com.android.settings.hardware.VibratorIntensity; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.livedisplay.DisplayGamma; +import com.android.settings.location.LocationSettings; public class BootReceiver extends BroadcastReceiver { @@ -40,5 +41,6 @@ public class BootReceiver extends BroadcastReceiver { VibratorIntensity.restore(ctx); DisplaySettings.restore(ctx); InputMethodAndLanguageSettings.restore(ctx); + LocationSettings.restore(ctx); } } diff --git a/src/com/android/settings/cyanogenmod/LtoService.java b/src/com/android/settings/cyanogenmod/LtoService.java new file mode 100644 index 0000000..8bbf8c8 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/LtoService.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * 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.android.settings.cyanogenmod; + +import static android.hardware.CmHardwareManager.FEATURE_LONG_TERM_ORBITS; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.hardware.CmHardwareManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.AsyncTask; +import android.os.IBinder; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.UserHandle; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.android.settings.location.LocationSettings; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; + +public class LtoService extends Service { + private static final String TAG = "LtoService"; + private static final boolean ALOGV = true; + + private static final String KEY_LAST_DOWNLOAD = "lto_last_download"; + + public static final String ACTION_NEW_GPS_DATA = "com.cyanogenmod.actions.NEW_GPS_DATA"; + + private static final int DOWNLOAD_TIMEOUT = 45000; /* 45 seconds */ + + private CmHardwareManager mCmHardwareManager; + private LtoDownloadTask mTask; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (mCmHardwareManager == null || + !mCmHardwareManager.isSupported(FEATURE_LONG_TERM_ORBITS)) { + if (ALOGV) Log.v(TAG, "LTO is not supported by this device"); + return START_NOT_STICKY; + } + if (!LocationSettings.isLocationModeEnabled(this)) { + if (ALOGV) Log.v(TAG, "Location mode not enabled in this device"); + return START_NOT_STICKY; + } + + if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) { + if (ALOGV) Log.v(TAG, "LTO download is still active, not starting new download"); + return START_REDELIVER_INTENT; + } + + if (!shouldDownload()) { + Log.d(TAG, "Service started, but shouldn't download ... stopping"); + stopSelf(); + return START_NOT_STICKY; + } + + mTask = new LtoDownloadTask(mCmHardwareManager.getLtoSource(), + new File(mCmHardwareManager.getLtoDestination())); + mTask.execute(); + + return START_REDELIVER_INTENT; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + mCmHardwareManager = (CmHardwareManager) getSystemService(Context.CMHW_SERVICE); + } + + @Override + public void onDestroy() { + if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) { + mTask.cancel(true); + mTask = null; + } + } + + private boolean shouldDownload() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + + if (info == null || !info.isConnected()) { + if (ALOGV) Log.v(TAG, "No network connection is available for LTO download"); + } else { + boolean wifiOnly = prefs.getBoolean( + LocationSettings.KEY_LTO_DOWNLOAD_DATA_WIFI_ONLY, false); + if (wifiOnly && info.getType() != ConnectivityManager.TYPE_WIFI) { + if (ALOGV) { + Log.v(TAG, "Active network is of type " + + info.getTypeName() + ", but Wifi only was selected"); + } + return false; + } + } + + long now = System.currentTimeMillis(); + long lastDownload = getLastDownload(); + long due = lastDownload + mCmHardwareManager.getLtoDownloadInterval(); + + if (ALOGV) { + Log.v(TAG, "Now " + now + " due " + due + "(" + new Date(due) + ")"); + } + + if (lastDownload != 0 && now < due) { + if (ALOGV) Log.v(TAG, "LTO download is not due yet"); + return false; + } + + return true; + } + + private class LtoDownloadTask extends AsyncTask<Void, Integer, Integer> { + private String mSource; + private File mDestination; + private File mTempFile; + private WakeLock mWakeLock; + + private static final int RESULT_SUCCESS = 0; + private static final int RESULT_FAILURE = 1; + private static final int RESULT_CANCELLED = 2; + + public LtoDownloadTask(String source, File destination) { + mSource = source; + mDestination = destination; + try { + mTempFile = File.createTempFile("lto-download", null, getCacheDir()); + } catch (IOException e) { + Log.w(TAG, "Could not create temporary file", e); + } + + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + } + + @Override + protected void onPreExecute() { + mWakeLock.acquire(); + } + + @Override + protected Integer doInBackground(Void... params) { + BufferedInputStream in = null; + BufferedOutputStream out = null; + int result = RESULT_SUCCESS; + + try { + final URLConnection connection = new URL(mSource).openConnection(); + connection.setRequestProperty("Connection", "close"); + connection.setConnectTimeout(DOWNLOAD_TIMEOUT); + connection.setReadTimeout(DOWNLOAD_TIMEOUT); + final Thread interruptThread = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(DOWNLOAD_TIMEOUT); + ((HttpURLConnection)connection).disconnect(); + } catch (InterruptedException e) { + } + } + }); + interruptThread.start(); + + File outputFile = mTempFile != null ? mTempFile : mDestination; + + in = new BufferedInputStream(connection.getInputStream()); + out = new BufferedOutputStream(new FileOutputStream(outputFile)); + + byte[] buffer = new byte[2048]; + int count, total = 0; + long length = 0; + try { + String value = connection.getHeaderField("Content-Length"); + if (value != null) { + length = Long.parseLong(value); + } + } catch (NumberFormatException ex) { + // Ignore + } + + // Read all the buffer + while ((count = in.read(buffer, 0, buffer.length)) != -1) { + if (isCancelled()) { + result = RESULT_CANCELLED; + break; + } + out.write(buffer, 0, count); + total += count; + + if (length > 0) { + float progress = (float) total * 100 / length; + publishProgress((int) progress); + } + } + + // The file is currently downloaded. Interrupt the timeout thread + interruptThread.interrupt(); + + Log.d(TAG, "Downloaded " + total + "/" + length + " bytes of LTO data"); + if (total == 0 || (length > 0 && total != length)) { + result = RESULT_FAILURE; + } + in.close(); + out.close(); + } catch (MalformedURLException e) { + Log.e(TAG, "URI syntax wrong", e); + result = RESULT_FAILURE; + } catch (IOException e) { + Log.e(TAG, "Failed downloading LTO data", e); + result = RESULT_FAILURE; + } finally { + try { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + Log.d(TAG, "return " + result); + return result; + } + + @Override + protected void onPostExecute(Integer result) { + if (result != null) { + finish(result); + } + } + + @Override + protected void onCancelled() { + finish(RESULT_CANCELLED); + } + + private void finish(int result) { + final Context context = LtoService.this; + + if (mTempFile != null) { + if (result == RESULT_SUCCESS) { + mDestination.delete(); + if (!mTempFile.renameTo(mDestination)) { + Log.w(TAG, "Could not move temporary file to destination"); + } else { + mDestination.setReadable(true, false); + } + } + mTempFile.delete(); + } else if (result != RESULT_SUCCESS) { + mDestination.delete(); + } else { + mDestination.setReadable(true, false); + } + + if (result == RESULT_SUCCESS) { + long now = System.currentTimeMillis(); + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putLong(KEY_LAST_DOWNLOAD, now); + editor.apply(); + scheduleNextDownload(now); + notifyNewGpsData(); + + } else if (result == RESULT_FAILURE) { + /* failure, schedule next download in 1 hour */ + long lastDownload = getLastDownload() + (60 * 60 * 1000); + scheduleNextDownload(lastDownload); + } else { + /* cancelled, likely due to lost network - we'll get restarted + * when network comes back */ + } + + mWakeLock.release(); + stopSelf(); + } + } + + private void notifyNewGpsData() { + Intent intent = new Intent(ACTION_NEW_GPS_DATA); + sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + + private PendingIntent scheduleNextDownload(long lastDownload) { + AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(this, LtoService.class); + PendingIntent pi = PendingIntent.getService(this, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT); + + long nextLtoDownload = lastDownload + mCmHardwareManager.getLtoDownloadInterval(); + am.set(AlarmManager.RTC, nextLtoDownload, pi); + return pi; + } + + private long getLastDownload() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + return prefs.getLong(LtoService.KEY_LAST_DOWNLOAD, 0); + } +} diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index b78cd31..d3f0ac8 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2015 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +17,17 @@ package com.android.settings.location; +import static android.hardware.CmHardwareManager.FEATURE_LONG_TERM_ORBITS; + +import android.app.AlarmManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.hardware.CmHardwareManager; import android.location.SettingInjectorService; import android.os.Bundle; import android.os.UserHandle; @@ -27,7 +35,10 @@ import android.os.UserManager; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceGroup; +import android.preference.PreferenceManager; import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.preference.Preference.OnPreferenceChangeListener; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -37,6 +48,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; +import com.android.settings.cyanogenmod.LtoService; import com.android.settings.widget.SwitchBar; import java.util.Collections; @@ -69,7 +81,7 @@ import java.util.List; * implementation. */ public class LocationSettings extends LocationSettingsBase - implements SwitchBar.OnSwitchChangeListener { + implements SwitchBar.OnSwitchChangeListener, OnPreferenceChangeListener { private static final String TAG = "LocationSettings"; @@ -93,12 +105,16 @@ public class LocationSettings extends LocationSettingsBase private static final int MENU_SCANNING = Menu.FIRST; + /** Key for preference LTO over Wi-Fi only */ + public static final String KEY_LTO_DOWNLOAD_DATA_WIFI_ONLY = "lto_download_data_wifi_only"; + private SwitchBar mSwitchBar; private Switch mSwitch; private boolean mValidListener = false; private UserHandle mManagedProfile; private Preference mManagedProfilePreference; private Preference mLocationMode; + private SwitchPreference mLtoDownloadDataWifiOnly; private PreferenceCategory mCategoryRecentLocationRequests; /** Receives UPDATE_INTENT */ private BroadcastReceiver mReceiver; @@ -191,6 +207,17 @@ public class LocationSettings extends LocationSettingsBase } }); + mLtoDownloadDataWifiOnly = + (SwitchPreference) root.findPreference(KEY_LTO_DOWNLOAD_DATA_WIFI_ONLY); + if (mLtoDownloadDataWifiOnly != null) { + if (!isLtoSupported(activity) || !checkGpsDownloadWiFiOnly(activity)) { + root.removePreference(mLtoDownloadDataWifiOnly); + mLtoDownloadDataWifiOnly = null; + } else { + mLtoDownloadDataWifiOnly.setOnPreferenceChangeListener(this); + } + } + mCategoryRecentLocationRequests = (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS); RecentLocationApps recentApps = new RecentLocationApps(activity); @@ -334,6 +361,9 @@ public class LocationSettings extends LocationSettingsBase // Disable the whole switch bar instead of the switch itself. If we disabled the switch // only, it would be re-enabled again if the switch bar is not disabled. mSwitchBar.setEnabled(!restricted); + if (mLtoDownloadDataWifiOnly != null) { + mLtoDownloadDataWifiOnly.setEnabled(enabled && !restricted); + } mLocationMode.setEnabled(enabled && !restricted); mCategoryRecentLocationRequests.setEnabled(enabled); @@ -377,4 +407,68 @@ public class LocationSettings extends LocationSettingsBase setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF); } } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mLtoDownloadDataWifiOnly != null && preference.equals(mLtoDownloadDataWifiOnly)) { + updateLtoServiceStatus(getActivity(), isLocationModeEnabled(getActivity())); + } + return true; + } + + private static void updateLtoServiceStatus(Context context, boolean start) { + Intent intent = new Intent(context, LtoService.class); + if (start) { + context.startService(intent); + } else { + context.stopService(intent); + } + } + + private static boolean checkGpsDownloadWiFiOnly(Context context) { + PackageManager pm = context.getPackageManager(); + boolean supportsTelephony = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + boolean supportsWifi = pm.hasSystemFeature(PackageManager.FEATURE_WIFI); + if (!supportsWifi || !supportsTelephony) { + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putBoolean(KEY_LTO_DOWNLOAD_DATA_WIFI_ONLY, supportsWifi); + editor.apply(); + return false; + } + return true; + } + + public static boolean isLocationModeEnabled(Context context) { + int mode = android.provider.Settings.Secure.getInt(context.getContentResolver(), + android.provider.Settings.Secure.LOCATION_MODE, + android.provider.Settings.Secure.LOCATION_MODE_OFF); + return (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF); + } + + /** + * Restore the properties associated with this preference on boot + * @param ctx A valid context + */ + public static void restore(final Context context) { + if (isLtoSupported(context) && isLocationModeEnabled(context)) { + // Check and adjust the value for Gps download data on wifi only + checkGpsDownloadWiFiOnly(context); + + // Starts the LtoService, but delayed 2 minutes after boot (this should give a + // proper time to start all device services) + AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, LtoService.class); + PendingIntent pi = PendingIntent.getService(context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT); + long nextLtoDownload = System.currentTimeMillis() + (1000 * 60 * 2L); + am.set(AlarmManager.RTC, nextLtoDownload, pi); + } + } + + private static boolean isLtoSupported(Context context) { + final CmHardwareManager hwManager = + (CmHardwareManager) context.getSystemService(Context.CMHW_SERVICE); + return hwManager != null && hwManager.isSupported(FEATURE_LONG_TERM_ORBITS); + } } |