/* * Tencent is pleased to support the open source community by making * Tencent GT (Version 2.4 and subsequent versions) available. * * Notwithstanding anything to the contrary herein, any previous version * of Tencent GT shall not be subject to the license hereunder. * All right, title, and interest, including all intellectual property rights, * in and to the previous version of Tencent GT (including any and all copies thereof) * shall be owned and retained by Tencent and subject to the license under the * Tencent GT End User License Agreement (http://gt.qq.com/wp-content/EULA_EN.html). * * Copyright (C) 2015 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the MIT License (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * * http://opensource.org/licenses/MIT * * 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.tencent.wstt.gt.plugin.gps; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Locale; import com.tencent.wstt.gt.GTApp; import com.tencent.wstt.gt.R; import com.tencent.wstt.gt.api.utils.Env; import com.tencent.wstt.gt.plugin.BaseService; import com.tencent.wstt.gt.utils.FileUtil; import com.tencent.wstt.gt.utils.GTUtils; import com.tencent.wstt.gt.utils.ToastUtil; import android.content.Context; import android.content.Intent; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; import android.os.AsyncTask; import android.os.IBinder; import android.util.Log; /** * GPS回放引擎 */ public class GTGPSReplayEngine extends BaseService { private static GTGPSReplayEngine INSTANCE; private MockGpsProvider mMockGpsProviderTask = null; private LocationManager locationManager = null; private List<GPSReplayListener> listeners; private boolean isReplay = false; public String selectedItem; public int selectedItemPos = -1; /*回放gps文件的总长度*/ public int mGPSFileLength = 0; /*回放gps文件的当前进度*/ public int index = 0; /*回放速率*/ public int mreplayspeed; private static final String GPS_MOCK_ACTION = "com.tencent.wstt.gt.ACTION_GPS_MOCK"; public static GTGPSReplayEngine getInstance() { if (null == INSTANCE) { INSTANCE = new GTGPSReplayEngine(); } return INSTANCE; } private GTGPSReplayEngine() { listeners = new ArrayList<GPSReplayListener>(); } public synchronized void addListener(GPSReplayListener listener) { listeners.add(listener); } public synchronized void removeListener(GPSReplayListener listener) { listeners.remove(listener); } synchronized public boolean isReplay() { return isReplay; } @Override public void onCreate(Context context) { super.onCreate(context); locationManager = (LocationManager) context .getSystemService(Context.LOCATION_SERVICE); } @Override public void onStart(Intent intent) { super.onStart(intent); if (null == intent) { return; } selectedItemPos = intent.getIntExtra("seq", -1); int progess = Math.min(100, intent.getIntExtra("progress", 0)); mreplayspeed = intent.getIntExtra("replayspeed", 1); index = getGPSFileLength() * progess / 100; if (-1 == selectedItemPos) { selectedItem = intent.getStringExtra("filename"); if (null == selectedItem) { // 还有一种可能是直接提供给服务的是经纬度坐标 String lng = intent.getStringExtra("lng"); String lat = intent.getStringExtra("lat"); if (lng == null || lat == null) { // 通知观察者需要选择一个文件或文件序号 for (GPSReplayListener listener : listeners) { listener.onReplayFail( GTApp.getContext().getString(R.string.pi_gps_replay_tip)); } } else { replay(lng, lat); } } else { // 按文件名回放 replay(selectedItem); } } else // 按序号回放 { // 先找好对应的文件名,再按文件名回放 ArrayList<String> items = GTGPSUtils.getGPSFileList(); if (items.size() > 0 && items.size() > selectedItemPos && !items.get(0).equals("empty")) replay(items.get(selectedItemPos)); } } @Override public void onDestroy() { super.onDestroy(); stopReplay(); // 停止回放 } @Override public IBinder onBind() { return null; } private boolean isAllowMock() { if (!locationManager.isProviderEnabled(MockGpsProvider.GPS_MOCK_PROVIDER)) { try { locationManager.addTestProvider(MockGpsProvider.GPS_MOCK_PROVIDER, false, false, false, false, true, false, false, 0, 5); locationManager.setTestProviderEnabled( MockGpsProvider.GPS_MOCK_PROVIDER, true); } catch(SecurityException e) { // 如果之前未在开发者选项中手动打开“允许模拟GPS”,这里可能会抛安全异常 for (GPSReplayListener listener : listeners) { listener.onReplayFail( GTApp.getContext().getString(R.string.pi_gps_warn_tip)); } return false; } } return true; } /* * 模拟指定点 */ private void replay(String sLng, String sLat) { if (! isAllowMock()) { return; } if (locationManager.isProviderEnabled(MockGpsProvider.GPS_MOCK_PROVIDER)) { try { double lng = Double.parseDouble(sLng); double lat = Double.parseDouble(sLat); // check坐标点的合法性 if (lng < -180 || lng > 180 || lat < -90 || lat > 90) { // 通知观察者 for (GPSReplayListener listener : listeners) { listener.onReplayFail( GTApp.getContext().getString(R.string.pi_gps_warn_tip2)); } return; } // 为了使用AsyncTask需要转普通数组 StringBuilder sb = new StringBuilder(); sb.append(lng); sb.append(","); sb.append(lat); sb.append(","); sb.append(0); sb.append(","); sb.append(0); sb.append(","); sb.append(0); sb.append(","); sb.append(0); // 时间,不过不关注填啥 sb.append(","); sb.append(0); sb.append(","); sb.append(0); String[] coordinates = new String[]{sb.toString()}; if (mMockGpsProviderTask == null) { String replayRecordFileName = GTUtils.getSaveDate() + "_.gps"; mMockGpsProviderTask = new MockGpsProvider(replayRecordFileName); } isReplay = true; mMockGpsProviderTask.execute(coordinates); } catch (Exception e) { isReplay = false; return; } } } /* * 模拟轨迹 */ private void replay(String fileName) { if (! isAllowMock()) { return; } if (locationManager.isProviderEnabled(MockGpsProvider.GPS_MOCK_PROVIDER)) { BufferedReader reader= null; try { List<String> data = new ArrayList<String>(); File f = new File(Env.ROOT_GPS_FOLDER, fileName); InputStream is = new FileInputStream(f); reader = new BufferedReader(new InputStreamReader(is)); String line = null; while ((line = reader.readLine()) != null) { data.add(line); } if (data.size() == 0) { // 通知观察者 for (GPSReplayListener listener : listeners) { listener.onReplayFail( GTApp.getContext().getString(R.string.pi_gps_warn_tip2)); } return; } mGPSFileLength = data.size(); // 为了使用AsyncTask需要转普通数组 String[] coordinates = new String[mGPSFileLength]; data.toArray(coordinates); if (mMockGpsProviderTask == null) { mMockGpsProviderTask = new MockGpsProvider(selectedItem); } isReplay = true; mMockGpsProviderTask.execute(coordinates); } catch (Exception e) { isReplay = false; return; } finally { FileUtil.closeReader(reader); } } } private void stopReplay() { isReplay = false; try { stopMockLocation(); } catch (Exception e) { } } private void sendMockBroadcast(Context context, String type) { Intent intent = new Intent(); intent.setAction(GPS_MOCK_ACTION); intent.putExtra("type", type); context.sendBroadcast(intent); } /** * add on 20140630 * 退出应用前也需要调用停止模拟位置,否则手机的正常GPS定位不会恢复 */ public void stopMockLocation() { try { mMockGpsProviderTask.cancel(true); mMockGpsProviderTask = null; } catch (Exception e) { } try { LocationManager locationManager = (LocationManager) GTApp.getContext().getSystemService(Context.LOCATION_SERVICE); locationManager .removeTestProvider(MockGpsProvider.GPS_MOCK_PROVIDER); } catch (Exception e) { } sendMockBroadcast(GTApp.getContext(), "stop"); } /** * 得带当前GPS回放到了百分之多少 */ public double getPercentage() { if (mGPSFileLength != 0 && index != 0) { return ((double) index / (double) mGPSFileLength); } return 0.0; } /** * 得带当前GPS回放速率 */ public int getReplaySpeed() { return mreplayspeed; } /** * 得带当前GPS回放文件的总数 */ public int getGPSFileLength() { return mGPSFileLength; } private class MockGpsProvider extends AsyncTask<String, Integer, Void> { public static final String LOG_TAG = "GpsMockProvider"; public static final String GPS_MOCK_PROVIDER = LocationManager.GPS_PROVIDER; public String orgiFileName; // public Integer index = 0; public MockGpsProvider(String fileName) { this.orgiFileName = fileName; } @Override protected Void doInBackground(String... data) { boolean hasMockEnd = false; double nowtimeStamp; List<Long> timezones = new ArrayList<Long>(); sendMockBroadcast(GTApp.getContext(), "start"); for (GPSReplayListener listener : listeners) { listener.onReplayStart(); } /* * 修改为保持最后1点的位置 */ while(true) { if (!isReplay()) { break; } String str = data[index];// 先获取当前点,下一句就index就切到下一个了 if (index < data.length - 1) // 到最后一点就不加序号了 { // add on 20141216 赶在index++前把本点回放的时间记录了 timezones.add(System.currentTimeMillis()); if (index + mreplayspeed > data.length - 1) { index++; } else { index += mreplayspeed; } } // add on 20150108 到最后一点立即发出广播通知测试程序回放逻辑已结束 else if (!hasMockEnd) { sendMockBroadcast(GTApp.getContext(), "end"); hasMockEnd = true; for (GPSReplayListener listener : listeners) { listener.onReplayEnd(); } } Location location = new Location(GPS_MOCK_PROVIDER); try { String[] parts = str.split(","); nowtimeStamp = Double.valueOf(parts[6]); location.setTime(System.currentTimeMillis()); location.setLatitude(Double.valueOf(parts[1])); location.setLongitude(Double.valueOf(parts[0])); location.setAccuracy((Float.valueOf(parts[2]))); location.setAltitude(Double.valueOf(parts[7])); location.setBearing(Float.valueOf(parts[3])); location.setSpeed(Float.valueOf(parts[4])); /* * 因为setElapsedRealtimeNanos在Android4.0后才支持, * 为了避免Android4.0之后报异常,用反射的方式补完location对象 * location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); */ try { Method method = Location.class.getMethod("makeComplete"); if (method != null) { method.invoke(location); } } catch (NoSuchMethodException e) { // Andorid4.0以下没有这个方法,直接跳过即可 } catch (Exception e) { e.printStackTrace(); } } catch (Exception e) { break; } Log.i(LOG_TAG, location.toString()); // 提供新的位置信息 try { locationManager.addTestProvider( LocationManager.GPS_PROVIDER, "requiresNetwork" == "", "requiresSatellite" == "", "requiresCell" == "", "hasMonetaryCost" == "", "supportsAltitude" == "supportsAltitude", "supportsSpeed" == "supportsSpeed", "supportsBearing" == "supportsBearing", android.location.Criteria.POWER_LOW, android.location.Criteria.ACCURACY_FINE); locationManager.setTestProviderStatus(GPS_MOCK_PROVIDER, LocationProvider.AVAILABLE, null, System.currentTimeMillis()); locationManager.setTestProviderLocation(GPS_MOCK_PROVIDER, location); } catch(Exception e) { // 如果未开位置模拟,这里可能出异常 ToastUtil.ShowLongToast(GTApp.getContext(), GTApp.getContext().getString(R.string.pi_gps_warn_tip)); break; } // 如果是最后一点,默认停2s if (index == data.length - 1) { try { Thread.sleep(2000); } catch (InterruptedException e) { } } else { // 如果是最后一点,间隔默认按2s算 int interval = 2000; try { if (index < data.length) { String next = data[index]; String[] parts = next.split(","); interval = (int) ((Double.valueOf(parts[6]) - nowtimeStamp) * 1000); if (interval <= 0) // 针对复制的数据,时间间隔没错开的情况的保护 { interval = 2000; } } Log.i("interval", String.valueOf(interval)); if (mreplayspeed == 1) { Thread.sleep(interval); } else { Thread.sleep(1000); } if (Thread.currentThread().isInterrupted()) throw new InterruptedException(""); } catch (InterruptedException e) { break; } } } index = 0; for (GPSReplayListener listener : listeners) { listener.onReplayStop(); } /* * add on 20141226 将回放位置的时刻记录在文件中,以便使用模拟位置的应用核对事件发生时的位置 * TODO 回放记录放在GT/Log/gpsreplay/<原回放文件名>_log.gps */ File folder = new File(Env.S_ROOT_LOG_FOLDER + "gpsreplay/"); folder.mkdirs(); // GT/Log/gpsreplay/xxxx_log.gps String recordFileName = orgiFileName; if (orgiFileName.toLowerCase(Locale.ENGLISH).endsWith(".gps")) { recordFileName = orgiFileName.substring(0, orgiFileName.length() - 4) + "_log.gps"; } File f = new File(folder, recordFileName); BufferedWriter bw = null; try { f.createNewFile(); bw = new BufferedWriter(new FileWriter(f)); for(int i = 0; i < timezones.size(); i++) { String relayLine = timezones.get(i) + "," + data[i].trim() + "\r\n"; bw.write(relayLine); } } catch (IOException e1) { e1.printStackTrace(); } finally { FileUtil.closeWriter(bw); } return null; } @Override protected void onProgressUpdate(Integer... values) { Log.d(LOG_TAG, "onProgressUpdate():" + values[0]); } } }