index.uts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. import Build from 'android.os.Build'
  2. import Intent from 'android.content.Intent'
  3. import Settings from 'android.provider.Settings'
  4. import Uri from 'android.net.Uri'
  5. import ContextCompat from 'androidx.core.content.ContextCompat'
  6. import ActivityCompat from 'androidx.core.app.ActivityCompat'
  7. import PackageManager from 'android.content.pm.PackageManager'
  8. import BluetoothAdapter from 'android.bluetooth.BluetoothAdapter'
  9. import { UTSAndroid } from 'io.dcloud.uts'
  10. import {
  11. BluetoothDemoInfo,
  12. CheckPermission,
  13. CheckPermissionOptions,
  14. CheckPermissions,
  15. CheckPermissionsOptions,
  16. CheckPermissionsResult,
  17. CheckPermissionsSync,
  18. CheckPermissionSync,
  19. GetSupportedPermissionsResult,
  20. GetSupportedPermissionsSync,
  21. GetBluetoothDemoInfoSync,
  22. OpenAppSettings,
  23. OpenAppSettingsOptions,
  24. PermissionCheckResult,
  25. PermissionFail,
  26. PermissionKey,
  27. PermissionGrantResult,
  28. PermissionStatus,
  29. RequestPermission,
  30. RequestPermissionOptions,
  31. RequestPermissions,
  32. RequestPermissionsOptions,
  33. RequestPermissionsResult,
  34. } from '../interface.uts'
  35. import { PermissionFailImpl } from '../unierror'
  36. const ANDROID_API_TIRAMISU : Int = 33
  37. const ANDROID_API_S : Int = 31
  38. function getAppTargetSdkVersion() : Int {
  39. const activity = UTSAndroid.getUniActivity()
  40. if (activity == null) {
  41. return 0
  42. }
  43. return activity.getApplicationInfo().targetSdkVersion
  44. }
  45. /** 设备 API 与宿主 targetSdkVersion 均满足时才使用 READ_MEDIA_*(UTS 运行时强校验 targetSdk) */
  46. function shouldUseReadMediaPermissions() : boolean {
  47. if (Build.VERSION.SDK_INT < ANDROID_API_TIRAMISU) {
  48. return false
  49. }
  50. return getAppTargetSdkVersion() >= ANDROID_API_TIRAMISU
  51. }
  52. /** 设备 API 与宿主 targetSdkVersion 均满足时才使用 BLUETOOTH_CONNECT / BLUETOOTH_SCAN */
  53. function shouldUseBluetoothRuntimePermissions() : boolean {
  54. if (Build.VERSION.SDK_INT < ANDROID_API_S) {
  55. return false
  56. }
  57. return getAppTargetSdkVersion() >= ANDROID_API_S
  58. }
  59. const SUPPORTED_PERMISSIONS : PermissionKey[] = [
  60. 'camera',
  61. 'photoLibrary',
  62. 'storage',
  63. 'readStorage',
  64. 'location',
  65. 'coarseLocation',
  66. 'microphone',
  67. 'contacts',
  68. 'writeContacts',
  69. 'bluetooth',
  70. 'phone',
  71. 'phoneState',
  72. 'sms',
  73. 'readSms',
  74. 'receiveSms',
  75. 'overlay',
  76. 'writeSettings',
  77. ]
  78. function createCheckResult(permission : PermissionKey, status : PermissionStatus) : PermissionCheckResult {
  79. return {
  80. permission: permission,
  81. status: status,
  82. granted: status == 'granted',
  83. }
  84. }
  85. function createRequestResult(permission : PermissionKey, status : PermissionStatus) : PermissionGrantResult {
  86. return {
  87. permission: permission,
  88. status: status,
  89. granted: status == 'granted',
  90. }
  91. }
  92. function resolveAndroidPermissions(permission : PermissionKey) : string[] | null {
  93. switch (permission) {
  94. case 'camera':
  95. return ['android.permission.CAMERA']
  96. case 'photoLibrary':
  97. if (shouldUseReadMediaPermissions()) {
  98. return ['android.permission.READ_MEDIA_IMAGES']
  99. }
  100. return ['android.permission.READ_EXTERNAL_STORAGE']
  101. case 'storage':
  102. if (shouldUseReadMediaPermissions()) {
  103. return ['android.permission.READ_MEDIA_IMAGES', 'android.permission.READ_MEDIA_VIDEO']
  104. }
  105. return ['android.permission.WRITE_EXTERNAL_STORAGE', 'android.permission.READ_EXTERNAL_STORAGE']
  106. case 'readStorage':
  107. if (shouldUseReadMediaPermissions()) {
  108. return ['android.permission.READ_MEDIA_IMAGES', 'android.permission.READ_MEDIA_VIDEO']
  109. }
  110. return ['android.permission.READ_EXTERNAL_STORAGE']
  111. case 'location':
  112. return ['android.permission.ACCESS_FINE_LOCATION']
  113. case 'coarseLocation':
  114. return ['android.permission.ACCESS_COARSE_LOCATION']
  115. case 'microphone':
  116. return ['android.permission.RECORD_AUDIO']
  117. case 'contacts':
  118. return ['android.permission.READ_CONTACTS']
  119. case 'writeContacts':
  120. return ['android.permission.WRITE_CONTACTS']
  121. case 'bluetooth':
  122. if (shouldUseBluetoothRuntimePermissions()) {
  123. return ['android.permission.BLUETOOTH_CONNECT', 'android.permission.BLUETOOTH_SCAN']
  124. }
  125. return ['android.permission.BLUETOOTH', 'android.permission.BLUETOOTH_ADMIN']
  126. case 'phone':
  127. return ['android.permission.CALL_PHONE']
  128. case 'phoneState':
  129. return ['android.permission.READ_PHONE_STATE']
  130. case 'sms':
  131. return ['android.permission.SEND_SMS']
  132. case 'readSms':
  133. return ['android.permission.READ_SMS']
  134. case 'receiveSms':
  135. return ['android.permission.RECEIVE_SMS']
  136. case 'overlay':
  137. case 'writeSettings':
  138. return []
  139. default:
  140. return null
  141. }
  142. }
  143. function isPermissionGranted(permission : PermissionKey) : boolean {
  144. const activity = UTSAndroid.getUniActivity()
  145. if (activity == null) {
  146. return false
  147. }
  148. if (permission == 'overlay') {
  149. return Settings.canDrawOverlays(activity)
  150. }
  151. if (permission == 'writeSettings') {
  152. return Settings.System.canWrite(activity)
  153. }
  154. const androidPermissions = resolveAndroidPermissions(permission)
  155. if (androidPermissions == null) {
  156. return false
  157. }
  158. if (androidPermissions.length == 0) {
  159. return false
  160. }
  161. return UTSAndroid.checkSystemPermissionGranted(activity, androidPermissions)
  162. }
  163. function resolvePermissionStatus(permission : PermissionKey) : PermissionStatus {
  164. const androidPermissions = resolveAndroidPermissions(permission)
  165. if (androidPermissions == null) {
  166. return 'unsupported'
  167. }
  168. if (permission == 'overlay' || permission == 'writeSettings') {
  169. return isPermissionGranted(permission) ? 'granted' : 'notDetermined'
  170. }
  171. const activity = UTSAndroid.getUniActivity()
  172. if (activity == null) {
  173. return 'denied'
  174. }
  175. let allGranted = true
  176. let hasDenied = false
  177. for (let i = 0; i < androidPermissions.length; i++) {
  178. const androidPermission = androidPermissions[i]
  179. const granted = ContextCompat.checkSelfPermission(activity, androidPermission) == PackageManager.PERMISSION_GRANTED
  180. if (granted) {
  181. continue
  182. }
  183. allGranted = false
  184. if (ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermission)) {
  185. hasDenied = true
  186. }
  187. }
  188. if (allGranted) {
  189. return 'granted'
  190. }
  191. if (hasDenied) {
  192. return 'denied'
  193. }
  194. return 'notDetermined'
  195. }
  196. function succeedCheckPermission(options : CheckPermissionOptions, res : PermissionCheckResult) {
  197. const successCallback = options.success
  198. if (successCallback != null) {
  199. successCallback(res)
  200. }
  201. const completeCallback = options.complete
  202. if (completeCallback != null) {
  203. completeCallback(res)
  204. }
  205. }
  206. function succeedCheckPermissions(options : CheckPermissionsOptions, res : CheckPermissionsResult) {
  207. const successCallback = options.success
  208. if (successCallback != null) {
  209. successCallback(res)
  210. }
  211. const completeCallback = options.complete
  212. if (completeCallback != null) {
  213. completeCallback(res)
  214. }
  215. }
  216. function succeedRequestPermission(options : RequestPermissionOptions, res : PermissionGrantResult) {
  217. const successCallback = options.success
  218. if (successCallback != null) {
  219. successCallback(res)
  220. }
  221. const completeCallback = options.complete
  222. if (completeCallback != null) {
  223. completeCallback(res)
  224. }
  225. }
  226. function succeedRequestPermissions(options : RequestPermissionsOptions, res : RequestPermissionsResult) {
  227. const successCallback = options.success
  228. if (successCallback != null) {
  229. successCallback(res)
  230. }
  231. const completeCallback = options.complete
  232. if (completeCallback != null) {
  233. completeCallback(res)
  234. }
  235. }
  236. function succeedOpenAppSettings(options : OpenAppSettingsOptions, res : PermissionCheckResult) {
  237. const successCallback = options.success
  238. if (successCallback != null) {
  239. successCallback(res)
  240. }
  241. const completeCallback = options.complete
  242. if (completeCallback != null) {
  243. completeCallback(res)
  244. }
  245. }
  246. function failRequestPermission(options : RequestPermissionOptions, err : PermissionFail) {
  247. const failCallback = options.fail
  248. if (failCallback != null) {
  249. failCallback(err)
  250. }
  251. const completeCallback = options.complete
  252. if (completeCallback != null) {
  253. completeCallback(err)
  254. }
  255. }
  256. function failRequestPermissions(options : RequestPermissionsOptions, err : PermissionFail) {
  257. const failCallback = options.fail
  258. if (failCallback != null) {
  259. failCallback(err)
  260. }
  261. const completeCallback = options.complete
  262. if (completeCallback != null) {
  263. completeCallback(err)
  264. }
  265. }
  266. function failOpenAppSettings(options : OpenAppSettingsOptions, err : PermissionFail) {
  267. const failCallback = options.fail
  268. if (failCallback != null) {
  269. failCallback(err)
  270. }
  271. const completeCallback = options.complete
  272. if (completeCallback != null) {
  273. completeCallback(err)
  274. }
  275. }
  276. function requestOverlayPermission(options : RequestPermissionOptions) {
  277. const activity = UTSAndroid.getUniActivity()
  278. if (activity == null) {
  279. failRequestPermission(options, new PermissionFailImpl(9020006, 'Activity 不可用'))
  280. return
  281. }
  282. if (Settings.canDrawOverlays(activity)) {
  283. succeedRequestPermission(options, createRequestResult(options.permission, 'granted'))
  284. return
  285. }
  286. const intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
  287. intent.setData(Uri.parse('package:' + activity.getPackageName()))
  288. activity.startActivity(intent)
  289. succeedRequestPermission(options, createRequestResult(options.permission, 'notDetermined'))
  290. }
  291. function requestWriteSettingsPermission(options : RequestPermissionOptions) {
  292. const activity = UTSAndroid.getUniActivity()
  293. if (activity == null) {
  294. failRequestPermission(options, new PermissionFailImpl(9020006, 'Activity 不可用'))
  295. return
  296. }
  297. if (Settings.System.canWrite(activity)) {
  298. succeedRequestPermission(options, createRequestResult(options.permission, 'granted'))
  299. return
  300. }
  301. const intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
  302. intent.setData(Uri.parse('package:' + activity.getPackageName()))
  303. activity.startActivity(intent)
  304. succeedRequestPermission(options, createRequestResult(options.permission, 'notDetermined'))
  305. }
  306. function requestRuntimePermission(options : RequestPermissionOptions) {
  307. const activity = UTSAndroid.getUniActivity()
  308. const androidPermissions = resolveAndroidPermissions(options.permission)
  309. if (activity == null) {
  310. failRequestPermission(options, new PermissionFailImpl(9020006, 'Activity 不可用'))
  311. return
  312. }
  313. if (androidPermissions == null) {
  314. failRequestPermission(options, new PermissionFailImpl(9020003))
  315. return
  316. }
  317. if (androidPermissions.length == 0) {
  318. failRequestPermission(options, new PermissionFailImpl(9020003))
  319. return
  320. }
  321. if (UTSAndroid.checkSystemPermissionGranted(activity, androidPermissions)) {
  322. succeedRequestPermission(options, createRequestResult(options.permission, 'granted'))
  323. return
  324. }
  325. try {
  326. UTSAndroid.requestSystemPermission(
  327. activity,
  328. androidPermissions,
  329. (allGranted : boolean, _ : string[]) => {
  330. if (allGranted) {
  331. succeedRequestPermission(options, createRequestResult(options.permission, 'granted'))
  332. return
  333. }
  334. succeedRequestPermission(options, createRequestResult(options.permission, 'denied'))
  335. },
  336. (_ : boolean, _grantedList : string[]) => {
  337. succeedRequestPermission(options, createRequestResult(options.permission, 'denied'))
  338. },
  339. )
  340. } catch (ignored) {
  341. failRequestPermission(options, new PermissionFailImpl(9020006, '权限申请失败,请确认 manifest 已声明对应权限且 targetSdkVersion 满足要求'))
  342. }
  343. }
  344. export const checkPermissionSync : CheckPermissionSync = function (permission : PermissionKey) : PermissionCheckResult {
  345. const status = resolvePermissionStatus(permission)
  346. return createCheckResult(permission, status)
  347. }
  348. export const checkPermissionsSync : CheckPermissionsSync = function (permissions : PermissionKey[]) : CheckPermissionsResult {
  349. const results : PermissionCheckResult[] = []
  350. let allGranted = true
  351. for (let i = 0; i < permissions.length; i++) {
  352. const item = checkPermissionSync(permissions[i])
  353. results.push(item)
  354. if (!item.granted) {
  355. allGranted = false
  356. }
  357. }
  358. return {
  359. results: results,
  360. allGranted: allGranted,
  361. }
  362. }
  363. export const checkPermission : CheckPermission = function (options : CheckPermissionOptions) {
  364. const result = checkPermissionSync(options.permission)
  365. succeedCheckPermission(options, result)
  366. }
  367. export const checkPermissions : CheckPermissions = function (options : CheckPermissionsOptions) {
  368. const result = checkPermissionsSync(options.permissions)
  369. succeedCheckPermissions(options, result)
  370. }
  371. export const requestPermission : RequestPermission = function (options : RequestPermissionOptions) {
  372. if (options.permission == 'overlay') {
  373. requestOverlayPermission(options)
  374. return
  375. }
  376. if (options.permission == 'writeSettings') {
  377. requestWriteSettingsPermission(options)
  378. return
  379. }
  380. requestRuntimePermission(options)
  381. }
  382. export const requestPermissions : RequestPermissions = function (options : RequestPermissionsOptions) {
  383. const permissions = options.permissions
  384. if (permissions.length == 0) {
  385. const emptyResult : RequestPermissionsResult = {
  386. results: [],
  387. allGranted: true,
  388. }
  389. succeedRequestPermissions(options, emptyResult)
  390. return
  391. }
  392. let currentIndex = 0
  393. const collectedResults : PermissionGrantResult[] = []
  394. function requestNext() {
  395. if (currentIndex >= permissions.length) {
  396. let allGranted = true
  397. for (let i = 0; i < collectedResults.length; i++) {
  398. if (!collectedResults[i].granted) {
  399. allGranted = false
  400. break
  401. }
  402. }
  403. const finalResult : RequestPermissionsResult = {
  404. results: collectedResults,
  405. allGranted: allGranted,
  406. }
  407. succeedRequestPermissions(options, finalResult)
  408. return
  409. }
  410. const permission = permissions[currentIndex]
  411. currentIndex += 1
  412. requestPermission({
  413. permission: permission,
  414. success: (res : PermissionGrantResult) => {
  415. collectedResults.push(res)
  416. requestNext()
  417. },
  418. fail: (err : PermissionFail) => {
  419. collectedResults.push(createRequestResult(permission, 'denied'))
  420. failRequestPermissions(options, err)
  421. },
  422. })
  423. }
  424. requestNext()
  425. }
  426. export const openAppSettings : OpenAppSettings = function (options : OpenAppSettingsOptions) {
  427. const activity = UTSAndroid.getUniActivity()
  428. if (activity == null) {
  429. failOpenAppSettings(options, new PermissionFailImpl(9020004, 'Activity 不可用'))
  430. return
  431. }
  432. try {
  433. const permission = options.permission
  434. let intent : Intent
  435. if (permission == 'overlay') {
  436. intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
  437. intent.setData(Uri.parse('package:' + activity.getPackageName()))
  438. } else if (permission == 'writeSettings') {
  439. intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
  440. intent.setData(Uri.parse('package:' + activity.getPackageName()))
  441. } else {
  442. intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
  443. intent.setData(Uri.parse('package:' + activity.getPackageName()))
  444. }
  445. activity.startActivity(intent)
  446. const resultPermission = permission != null ? permission : 'camera'
  447. succeedOpenAppSettings(options, createCheckResult(resultPermission, 'granted'))
  448. } catch (ignored) {
  449. failOpenAppSettings(options, new PermissionFailImpl(9020004))
  450. }
  451. }
  452. export const getSupportedPermissionsSync : GetSupportedPermissionsSync = function () : GetSupportedPermissionsResult {
  453. return {
  454. permissions: SUPPORTED_PERMISSIONS,
  455. }
  456. }
  457. function mapAndroidBluetoothState(state : Int) : string {
  458. switch (state) {
  459. case BluetoothAdapter.STATE_OFF:
  460. return '已关闭'
  461. case BluetoothAdapter.STATE_TURNING_ON:
  462. return '正在打开'
  463. case BluetoothAdapter.STATE_ON:
  464. return '已开启'
  465. case BluetoothAdapter.STATE_TURNING_OFF:
  466. return '正在关闭'
  467. default:
  468. return '未知'
  469. }
  470. }
  471. export const getBluetoothDemoInfoSync : GetBluetoothDemoInfoSync = function () : BluetoothDemoInfo {
  472. const adapter = BluetoothAdapter.getDefaultAdapter()
  473. if (adapter == null) {
  474. return {
  475. available: false,
  476. enabled: false,
  477. stateText: '设备不支持蓝牙',
  478. stateCode: -1,
  479. }
  480. }
  481. const state = adapter.getState()
  482. return {
  483. available: true,
  484. enabled: state == BluetoothAdapter.STATE_ON,
  485. stateText: mapAndroidBluetoothState(state),
  486. stateCode: state,
  487. }
  488. }