/* * Copyright (C) 2012-2015 Paul Watts (paulcwatts@gmail.com), University of South Florida * and individual contributors * * 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 org.onebusaway.android.region; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.api.GoogleApiClient; import org.onebusaway.android.R; import org.onebusaway.android.app.Application; import org.onebusaway.android.io.ObaAnalytics; import org.onebusaway.android.io.elements.ObaRegion; import org.onebusaway.android.util.LocationUtils; import org.onebusaway.android.util.RegionUtils; import org.onebusaway.android.util.UIUtils; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.location.Location; import android.os.AsyncTask; import android.os.Handler; import android.util.Log; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * AsyncTask used to refresh region info from the Regions REST API. * <p/> * Classes utilizing this task can request a callback via MapModeController.Callback.setMyLocation() * by passing in class implementing MapModeController.Callback in the constructor * * @author barbeau */ public class ObaRegionsTask extends AsyncTask<Void, Integer, ArrayList<ObaRegion>> { public interface Callback { /** * Called when the ObaRegionsTask is complete * * @param currentRegionChanged true if the current region changed as a result of the task, * false if it didn't change */ public void onRegionTaskFinished(boolean currentRegionChanged); } private static final String TAG = "ObaRegionsTask"; private final int CALLBACK_DELAY = 100; //in milliseconds private Context mContext; private ProgressDialog mProgressDialog; private String mProgressDialogMessage; private List<ObaRegionsTask.Callback> mCallbacks = new ArrayList<>(); private final boolean mForceReload; private final boolean mShowProgressDialog; /** * GoogleApiClient being used for Location Services */ GoogleApiClient mGoogleApiClient; /** * @param callbacks a callback will be made to all interfaces in this list after the task is * complete (null if no callbacks are requested) */ public ObaRegionsTask(Context context, List<ObaRegionsTask.Callback> callbacks) { mContext = context; mCallbacks = callbacks; mForceReload = false; mShowProgressDialog = true; } /** * @param callbacks a callback will be made to all interfaces in this list after the * task is complete (null if no callbacks are requested) * @param force true if the task should be forced to update region info from the * server, false if it can return local info * @param showProgressDialog true if a progress dialog should be shown to the user during the * task, false if it should not */ public ObaRegionsTask(Context context, List<ObaRegionsTask.Callback> callbacks, boolean force, boolean showProgressDialog) { mContext = context; mCallbacks = callbacks; mForceReload = force; mShowProgressDialog = showProgressDialog; GoogleApiAvailability api = GoogleApiAvailability.getInstance(); if (api.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) { mGoogleApiClient = LocationUtils.getGoogleApiClientWithCallbacks(context); mGoogleApiClient.connect(); } } @Override protected void onPreExecute() { if (mShowProgressDialog && UIUtils.canManageDialog(mContext)) { if (mProgressDialogMessage == null){ mProgressDialogMessage = mContext.getString(R.string.region_detecting_server); } mProgressDialog = ProgressDialog.show(mContext, "", mProgressDialogMessage, true); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(false); mProgressDialog.show(); } super.onPreExecute(); } @Override protected ArrayList<ObaRegion> doInBackground(Void... params) { return RegionUtils.getRegions(mContext, mForceReload); } @Override protected void onPostExecute(ArrayList<ObaRegion> results) { if (results == null) { //This is a catastrophic failure to load region info from all sources return; } // Dismiss the dialog before calling the callbacks to avoid errors referencing the dialog later if (mShowProgressDialog && UIUtils.canManageDialog(mContext) && mProgressDialog .isShowing()) { mProgressDialog.dismiss(); } SharedPreferences settings = Application.getPrefs(); if (settings .getBoolean(mContext.getString(R.string.preference_key_auto_select_region), true)) { // Pass in the GoogleApiClient initialized in constructor Location myLocation = Application.getLastKnownLocation(mContext, mGoogleApiClient); ObaRegion closestRegion = RegionUtils.getClosestRegion(results, myLocation, true); if (Application.get().getCurrentRegion() == null) { if (closestRegion != null) { //No region has been set, so set region application-wide to closest region Application.get().setCurrentRegion(closestRegion); Log.d(TAG, "Detected closest region '" + closestRegion.getName() + "'"); //Analytics ObaAnalytics.reportEventWithCategory(ObaAnalytics.ObaEventCategory.APP_SETTINGS.toString(), mContext.getString(R.string.analytics_action_configured_region_auto), mContext.getString(R.string.analytics_label_region_auto) + closestRegion.getName() + "; Old Region: null"); doCallback(true); } else { //No region has been set, and we couldn't find a usable region based on RegionUtil.isRegionUsable() //or we couldn't find a closest a region, so ask the user to pick the region haveUserChooseRegion(results); } } else if (Application.get().getCurrentRegion() != null && closestRegion != null && !Application.get().getCurrentRegion().equals(closestRegion)) { //User is closer to a different region than the current region, so change to the closest region String oldRegionName = Application.get().getCurrentRegion().getName(); Application.get().setCurrentRegion(closestRegion); Log.d(TAG, "Detected closer region '" + closestRegion.getName() + "', changed to this region."); //Analytics ObaAnalytics.reportEventWithCategory(ObaAnalytics.ObaEventCategory.APP_SETTINGS.toString(), mContext.getString(R.string.analytics_action_configured_region_auto) , mContext.getString(R.string.analytics_label_region_auto) + closestRegion.getName() + "; Old Region: " + oldRegionName); doCallback(true); } else if (Application.get().getCurrentRegion() != null && closestRegion != null && Application.get().getCurrentRegion().equals(closestRegion)) { // Don't change the region - just refresh with latest Regions API contents Application.get().setCurrentRegion(closestRegion, false); doCallback(false); } else { doCallback(false); } } else { if (Application.get().getCurrentRegion() == null) { //We don't have a region selected, and the user chose not to auto-select one, so make them pick one haveUserChooseRegion(results); } else { doCallback(false); } } // Tear down Location Services client if (mGoogleApiClient != null) { mGoogleApiClient.disconnect(); } super.onPostExecute(results); } private void haveUserChooseRegion(final ArrayList<ObaRegion> result) { if (!UIUtils.canManageDialog(mContext)) { return; } // Create dialog for user to choose List<String> serverNames = new ArrayList<String>(); for (ObaRegion region : result) { if (RegionUtils.isRegionUsable(region)) { serverNames.add(region.getName()); } } Collections.sort(serverNames); final CharSequence[] items = serverNames .toArray(new CharSequence[serverNames.size()]); AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle(mContext.getString(R.string.region_choose_region)); builder.setCancelable(false); builder.setItems(items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { for (ObaRegion region : result) { if (region.getName().equals(items[item])) { //Set the region application-wide Application.get().setCurrentRegion(region); Log.d(TAG, "User chose region '" + items[item] + "'."); doCallback(true); break; } } } }); AlertDialog alert = builder.create(); alert.show(); } private void doCallback(final boolean currentRegionChanged) { // If we execute on same thread immediately after setting Region, map UI may try to call // OBA REST API before the new region info is set in Application. So, pause briefly. final Handler mPauseForCallbackHandler = new Handler(); final Runnable mPauseForCallback = new Runnable() { public void run() { // Map may not have triggered call to OBA REST API, so we force one here if (mCallbacks != null) { for (Callback callback : mCallbacks) { if (callback != null) { callback.onRegionTaskFinished(currentRegionChanged); } } } } }; mPauseForCallbackHandler.postDelayed(mPauseForCallback, CALLBACK_DELAY); } public void setProgressDialogMessage(String progressDialogMessage) { mProgressDialogMessage = progressDialogMessage; } }