diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c9378b254649c8cb01dc7f28a41e33d94dc37a3f..5575ca1a46bee3c88af2757865851f4567af68f5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,8 @@ + + 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { this.sendBroadcast(new Intent(GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION)); } + } else if (requestCode == DEV_TYPE_LOCATION) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + this.sendBroadcast(new Intent(GRANT_LOCATION_PERMISSION_SUCCESS_ACTION)); + } } } diff --git a/cloudphone/src/main/cpp/cas_common/CasMsg.h b/cloudphone/src/main/cpp/cas_common/CasMsg.h index fcea1f5be0777b34083a66d444822351f2e52fe8..f591ff846bf06edc253bc95cfe1d0f24a3088df5 100644 --- a/cloudphone/src/main/cpp/cas_common/CasMsg.h +++ b/cloudphone/src/main/cpp/cas_common/CasMsg.h @@ -38,6 +38,7 @@ enum CasMsgType : uint8_t { VirtualCamera = 21, VirtualMicrophone = 22, VirtualSensor = 23, + VirtualLocation = 24, End, }; @@ -61,6 +62,7 @@ enum CasMsgType : uint8_t { #define CAS_MSG_CHECKSUM_VIRTUAL_CAMERA GET_CAS_CHECKSUM(CasMsgType::VirtualCamera) #define CAS_MSG_CHECKSUM_VIRTUAL_MICROPHONE GET_CAS_CHECKSUM(CasMsgType::VirtualMicrophone) #define CAS_MSG_CHECKSUM_VIRTUAL_SENSOR GET_CAS_CHECKSUM(CasMsgType::VirtualSensor) +#define CAS_MSG_CHECKSUM_VIRTUAL_LOCATION GET_CAS_CHECKSUM(CasMsgType::VirtualLocation) // 客户端通用消息头 typedef struct streamMsgHead { diff --git a/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp b/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp index 92bbf8a97847c424df782db2945b48f1be80f778..f857d95782c46f59b02e8bc852850148b49c6504 100644 --- a/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp +++ b/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp @@ -84,6 +84,9 @@ int CasStreamBuildSender::SendDataToServer(CasMsgType type, const void *buf, siz case (VirtualSensor): msgHead.checksum = CAS_MSG_CHECKSUM_VIRTUAL_SENSOR; break; + case (VirtualLocation): + msgHead.checksum = CAS_MSG_CHECKSUM_VIRTUAL_LOCATION; + break; default: { return -1; } diff --git a/cloudphone/src/main/cpp/cas_stream/CasStreamRecvParser.cpp b/cloudphone/src/main/cpp/cas_stream/CasStreamRecvParser.cpp index 60c1257cef0d7331ff5601900387e9f2673c5d21..09746b90d9944d7f2f12a1a03801807f5d773186 100644 --- a/cloudphone/src/main/cpp/cas_stream/CasStreamRecvParser.cpp +++ b/cloudphone/src/main/cpp/cas_stream/CasStreamRecvParser.cpp @@ -75,7 +75,7 @@ void CasStreamRecvParser::SetServiceHandle(unsigned char type, CasPktHandle *ser CasPktHandle *CasStreamRecvParser::GetServiceHandle(unsigned char type) { - return VirtualSensor >= type && type >= VirtualCamera ? m_serviceHandles[VirtualDevice] : m_serviceHandles[type]; + return VirtualLocation >= type && type >= VirtualCamera ? m_serviceHandles[VirtualDevice] : m_serviceHandles[type]; } CasStreamParseThread::CasStreamParseThread(CasSocket *socket, CasStreamRecvParser *streamRecvParser) diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java index 0ea60bdabcb5872f44cddc716d3088eb9337bc9c..99ccd8afdefaf9baecd15787451efe6bdab6b53b 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java @@ -56,6 +56,7 @@ public class JNIWrapper { public static final byte CAMERA_DATA = 21; public static final byte MICROPHONE_DATA = 22; public static final byte SENSOR_DATA = 23; + public static final byte LOCATION_DATA = 24; static { System.loadLibrary("cloudapp"); diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java index 48d8d5e4e3d0013b6358d8ec194b6ee743e6c4b2..5c046833a4e80d92bb68041556fc4ceb7185623d 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java @@ -21,6 +21,7 @@ public class VirtualDeviceManager { public static final short DEV_TYPE_CAMERA = 1; public static final short DEV_TYPE_MICROPHONE = 2; public static final short DEV_TYPE_SENSOR = 0; + public static final short DEV_TYPE_LOCATION = 4; public void processMsg(MsgHeader header, byte[] body) { } diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java index f4bfb0b06c73a906a148eae7dd7ab46a11b3bad1..1e5c85543585cb5b695cc78a098a6cdec26c2a2b 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java @@ -17,8 +17,10 @@ package com.huawei.cloudphone.virtualdevice.common; import static com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager.DEV_TYPE_CAMERA; import static com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager.GRANT_CAMERA_PERMISSION_SUCCESS_ACTION; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_LOCATION; import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_MICROPHONE; import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_SENSOR; +import static com.huawei.cloudphone.virtualdevice.location.VirtualLocationManager.GRANT_LOCATION_PERMISSION_SUCCESS_ACTION; import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophoneManager.GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION; import android.content.BroadcastReceiver; @@ -29,6 +31,8 @@ import android.hardware.SensorManager; import android.util.Log; import com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager; +import com.huawei.cloudphone.virtualdevice.location.VirtualLocation; +import com.huawei.cloudphone.virtualdevice.location.VirtualLocationManager; import com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophoneManager; import com.huawei.cloudphone.virtualdevice.sensor.VirtualSensorManager; @@ -52,6 +56,8 @@ public class VirtualDeviceProtocol { ((VirtualCameraManager)virtualDeviceManagers.get(DEV_TYPE_CAMERA)).initCamera(); } else if (GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION.equals(intent.getAction())) { ((VirtualMicrophoneManager)virtualDeviceManagers.get(DEV_TYPE_MICROPHONE)).initMicrophone(); + } else if (GRANT_LOCATION_PERMISSION_SUCCESS_ACTION.equals(intent.getAction())) { + ((VirtualLocationManager)virtualDeviceManagers.get(DEV_TYPE_LOCATION)).initLocation(); } } }; @@ -68,10 +74,12 @@ public class VirtualDeviceProtocol { virtualDeviceManagers.put(DEV_TYPE_MICROPHONE, new VirtualMicrophoneManager(this, mContext)); virtualDeviceManagers.put(DEV_TYPE_SENSOR, new VirtualSensorManager(this, (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE))); + virtualDeviceManagers.put(DEV_TYPE_LOCATION, new VirtualLocationManager(this, mContext)); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(GRANT_CAMERA_PERMISSION_SUCCESS_ACTION); intentFilter.addAction(GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION); + intentFilter.addAction(GRANT_LOCATION_PERMISSION_SUCCESS_ACTION); mContext.registerReceiver(mPermissionResultReceiver, intentFilter); } @@ -84,7 +92,6 @@ public class VirtualDeviceProtocol { virtualDeviceManager.processMsg(header, body); } - public void startProcess() { mIsTaskRun = true; mPktProcessThread = new PacketParseThread(); diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/location/VirtualLocation.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/location/VirtualLocation.java new file mode 100644 index 0000000000000000000000000000000000000000..a5a09f33b3e05bbf2abe9de36e4c97705b2cf1d6 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/location/VirtualLocation.java @@ -0,0 +1,230 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * 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.huawei.cloudphone.virtualdevice.location; + +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_LOCATION; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Looper; +import android.provider.Settings; +import android.telephony.CellIdentityCdma; +import android.telephony.CellIdentityGsm; +import android.telephony.CellIdentityLte; +import android.telephony.CellIdentityWcdma; +import android.telephony.CellInfo; +import android.telephony.CellInfoCdma; +import android.telephony.CellInfoGsm; +import android.telephony.CellInfoLte; +import android.telephony.CellInfoWcdma; +import android.telephony.TelephonyManager; + +import androidx.annotation.RequiresApi; +import androidx.core.app.ActivityCompat; + +import com.huawei.cloudphone.common.CASLog; +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; + +import java.util.ArrayList; +import java.util.List; + +public class VirtualLocation { + private static final String TAG = "VirtualLocation"; + private final long MIN_TIME = 1000; + private final float MIN_DISTANCE = 1; + + private Context mContext; + private Location mLocation; + private LocationManager mLocationManager; + private LocationListener mLocationListener; + private String mLocationProvider; + private IVirtualDeviceDataListener mListener = null; + + public VirtualLocation(Context context) { + mContext = context; + } + + public void registerLocationDataListener(IVirtualDeviceDataListener listener) { + mListener = listener; + } + + @RequiresApi(api = Build.VERSION_CODES.P) + @SuppressLint("MissingPermission") + public void requestLocationUpdates() { + if ((ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) + && (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)) { + CASLog.i(TAG, "request coarse and fine location permission"); + ActivityCompat.requestPermissions((Activity) mContext, + new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, + DEV_TYPE_LOCATION); + } else { + if (mLocationManager == null) { + mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + } + boolean locationEnable = mLocationManager.isLocationEnabled(); + if (!locationEnable) { + CASLog.i(TAG, "location enable is false"); + Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + mContext.startActivity(intent); + return; + } + + List providers = mLocationManager.getProviders(true); + if (providers.contains(LocationManager.GPS_PROVIDER)) { + mLocationProvider = LocationManager.GPS_PROVIDER; + } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) { + mLocationProvider = LocationManager.NETWORK_PROVIDER; + } else { + return; + } + CASLog.i(TAG, "location provider is " + mLocationProvider); + mLocationListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + if (location != null) { + mLocation = location; + String locationInfo = getLocationInfo(location); + mListener.onRecvData(locationInfo); + } + } + + @Override + public void onStatusChanged(String s, int i, Bundle bundle) { + } + + @Override + public void onProviderEnabled(String s) { + } + + @Override + public void onProviderDisabled(String s) { + } + }; + mLocationManager.requestLocationUpdates(mLocationProvider, MIN_TIME, MIN_DISTANCE, mLocationListener, Looper.getMainLooper()); + mLocation = mLocationManager.getLastKnownLocation(mLocationProvider); + String locationInfo = getLocationInfo(mLocation); + if (locationInfo != null) { + mListener.onRecvData(locationInfo); + } + } + } + + @SuppressLint("MissingPermission") + public void closeLocationUpdates() { + if (mLocationManager != null) { + mLocationManager.removeUpdates(mLocationListener); + } + if (mLocationListener != null) { + mLocationListener = null; + } + } + + private String getLocationInfo(Location location) { + if (location == null) { + return null; + } + String telephonyCellInfo = getTelephonyCellInfo(); + String locationInfo = "longitude=" + location.getLongitude() + + ":latitude=" + location.getLatitude() + + ":altitude=" + location.getAltitude() + + ":speed=" + location.getSpeed() + + ":bearing=" + location.getBearing() + + ":accuracy=" + location.getAccuracy(); + if (telephonyCellInfo != null) { + locationInfo = locationInfo + ":" + telephonyCellInfo; + } + return locationInfo; + } + + @SuppressLint("MissingPermission") + private String getTelephonyCellInfo() { + TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + String operator = telephonyManager.getNetworkOperator(); + int mcc = -1; + if (operator != null && operator.length() == 3) { + mcc = Integer.parseInt(operator.substring(0, 3)); + } + + List cellInfoDataList = new ArrayList<>(); + List cellInfoList = telephonyManager.getAllCellInfo(); + for (CellInfo cellInfo : cellInfoList) { + CellInfoData cellInfoData = new CellInfoData(); + cellInfoData.mcc = mcc; + if (cellInfo instanceof CellInfoCdma) { + CellInfoCdma cellInfoCdma = (CellInfoCdma) cellInfo; + CellIdentityCdma cellIdentityCdma = cellInfoCdma.getCellIdentity(); + cellInfoData.mnc = cellIdentityCdma.getSystemId(); + cellInfoData.lac = cellIdentityCdma.getNetworkId(); + cellInfoData.cid = cellIdentityCdma.getBasestationId(); + cellInfoData.cellType = "CDMA"; + } else if (cellInfo instanceof CellInfoGsm) { + CellInfoGsm cellInfoGsm = (CellInfoGsm) cellInfo; + CellIdentityGsm cellIdentityGsm = cellInfoGsm.getCellIdentity(); + cellInfoData.mnc = cellIdentityGsm.getMnc(); + cellInfoData.lac = cellIdentityGsm.getLac(); + cellInfoData.cid = cellIdentityGsm.getCid(); + cellInfoData.cellType = "GSM"; + } else if (cellInfo instanceof CellInfoLte) { + CellInfoLte cellInfoLte = (CellInfoLte) cellInfo; + CellIdentityLte cellIdentityLte = cellInfoLte.getCellIdentity(); + cellInfoData.mnc = cellIdentityLte.getMnc(); + cellInfoData.lac = cellIdentityLte.getTac(); + cellInfoData.cid = cellIdentityLte.getCi(); + cellInfoData.cellType = "LTE"; + } else if (cellInfo instanceof CellInfoWcdma) { + CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) cellInfo; + CellIdentityWcdma cellIdentityWcdma = null; + if (SDK_INT >= JELLY_BEAN_MR2) { + cellIdentityWcdma = cellInfoWcdma.getCellIdentity(); + cellInfoData.mnc = cellIdentityWcdma.getMnc(); + cellInfoData.lac = cellIdentityWcdma.getLac(); + cellInfoData.cid = cellIdentityWcdma.getCid(); + } + cellInfoData.cellType = "WCDMA"; + } else { + return null; + } + cellInfoDataList.add(cellInfoData); + } + if (cellInfoDataList.size() > 0) { + return cellInfoDataList.get(0).getCellInfo(); + } + return null; + } + + private class CellInfoData { + private String cellType = ""; + private int mcc = -1; + private int mnc = -1; + private int lac = -1; + private int cid = -1; + + private String getCellInfo() { + return String.format("cell_type=%s:mcc=%d:mnc=%d:lac=%d:cid=%d", cellType, mcc, mnc, lac, cid); + } + } +} \ No newline at end of file diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/location/VirtualLocationManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/location/VirtualLocationManager.java new file mode 100644 index 0000000000000000000000000000000000000000..c062b1fa1615a7c2015af530ec9753c43f74b198 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/location/VirtualLocationManager.java @@ -0,0 +1,71 @@ +package com.huawei.cloudphone.virtualdevice.location; + +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.LOCATION_DATA; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MSG_HEADER_LEN; + +import android.content.Context; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import com.huawei.cloudphone.common.CASLog; +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol; + +public class VirtualLocationManager extends VirtualDeviceManager { + private static final String TAG = "VirtualLocationManager"; + public static final short OPT_LOCATION_OPEN_REQ = 0x0; + public static final short OPT_LOCATION_CLOSE_REQ = 0x1; + public static final short OPT_LOCATION_DATA = 0x2; + public static final String GRANT_LOCATION_PERMISSION_SUCCESS_ACTION = "android.intent.action.GRANT_LOCATION_PERMISSION_SUCCESS"; + + private VirtualLocation mVirtualLocation; + private VirtualDeviceProtocol mVirtualDeviceProtocol; + + public VirtualLocationManager(VirtualDeviceProtocol virtualDeviceProtocol, Context context) { + mVirtualDeviceProtocol = virtualDeviceProtocol; + mVirtualLocation = new VirtualLocation(context); + mVirtualLocation.registerLocationDataListener(new LocationDataListener()); + } + + @RequiresApi(api = Build.VERSION_CODES.P) + public void initLocation() { + mVirtualLocation.requestLocationUpdates(); + } + + public void stop() { + mVirtualLocation.closeLocationUpdates(); + } + + @RequiresApi(api = Build.VERSION_CODES.P) + public void processMsg(VirtualDeviceProtocol.MsgHeader header, byte[] body) { + switch (header.mOptType) { + case OPT_LOCATION_OPEN_REQ: + Log.i(TAG, "processMsg: open location"); + mVirtualLocation.requestLocationUpdates(); + break; + case OPT_LOCATION_CLOSE_REQ: + Log.i(TAG, "processMsg: close location"); + mVirtualLocation.closeLocationUpdates(); + break; + default: + Log.e(TAG, "processMsg: error opt type"); + } + } + + class LocationDataListener implements IVirtualDeviceDataListener { + @Override + public void onRecvData(Object... args) { + String body = (String) args[0]; + int type = 0; + int bodyLen = body.getBytes().length; + int rspMsgLen = bodyLen + MSG_HEADER_LEN; + VirtualDeviceProtocol.MsgHeader header = new VirtualDeviceProtocol.MsgHeader(OPT_LOCATION_DATA, DEV_TYPE_LOCATION, (short) type, rspMsgLen); + byte[] rspBody = new byte[bodyLen]; + System.arraycopy(body.getBytes(), 0, rspBody, 0, bodyLen); + mVirtualDeviceProtocol.sendMsg(header, rspBody, LOCATION_DATA); + } + } +}