1 //! Bindings for `AConfiguration`.
2 //!
3 //! See also the [NDK docs](https://developer.android.com/ndk/reference/group/configuration) for
4 //! `AConfiguration`, as well as the [docs for providing
5 //! resources](https://developer.android.com/guide/topics/resources/providing-resources.html),
6 //! which explain many of the configuration values.  The [`android.content.res.Configuration`
7 //! javadoc](https://developer.android.com/reference/android/content/res/Configuration.html) may
8 //! also have useful information.
9 
10 use crate::asset::AssetManager;
11 use num_enum::{IntoPrimitive, TryFromPrimitive};
12 use std::convert::TryInto;
13 use std::fmt;
14 use std::ptr::NonNull;
15 
16 /// A native `AConfiguration *`.
17 ///
18 /// This stores information about configuration.  See [the NDK
seclabel_fournull19 /// docs](https://developer.android.com/ndk/reference/group/configuration)
20 pub struct Configuration {
21     ptr: NonNull<ffi::AConfiguration>,
22 }
23 
24 unsafe impl Send for Configuration {}
25 unsafe impl Sync for Configuration {}
26 
27 impl Drop for Configuration {
28     fn drop(&mut self) {
29         unsafe { ffi::AConfiguration_delete(self.ptr.as_ptr()) }
30     }
31 }
32 
33 impl Clone for Configuration {
34     fn clone(&self) -> Self {
35         let mut new = Self::new();
36         new.copy(self);
37         new
38     }
39 }
40 
41 impl PartialEq for Configuration {
42     fn eq(&self, other: &Self) -> bool {
43         self.diff(other).0 == 0
44     }
45 }
46 impl Eq for Configuration {}
47 
48 impl fmt::Debug for Configuration {
49     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50         f.debug_struct("Configuration")
51             .field("mcc", &self.mcc())
52             .field("mnc", &self.mnc())
53             .field("lang", &self.language())
54             .field("country", &self.country())
55             .field("orientation", &self.orientation())
56             .field("touchscreen", &self.touchscreen())
57             .field("density", &self.density())
58             .field("keyboard", &self.keyboard())
59             .field("navigation", &self.navigation())
60             .field("keys_hidden", &self.keys_hidden())
61             .field("nav_hidden", &self.nav_hidden())
62             .field("sdk_version", &self.sdk_version())
63             .field("screen_size", &self.screen_size())
64             .field("screen_long", &self.screen_long())
65             .field("ui_mode_type", &self.ui_mode_type())
66             .field("ui_mode_night", &self.ui_mode_night())
67             .finish()
68     }
69 }
70 
71 impl Configuration {
72     /// Construct a `Configuration` from a pointer.
73     ///
74     /// By calling this function, you assert that it is a valid pointer to a native
75     /// `AConfiguration`, and give ownership of it to the `Configuration` instance.
76     pub unsafe fn from_ptr(ptr: NonNull<ffi::AConfiguration>) -> Self {
77         Self { ptr }
78     }
79 
80     /// Create a new `Configuration`, with the same contents as the `AConfiguration` referenced by
81     /// the pointer.
82     ///
83     /// This is useful if you have a pointer, but not ownership of it.
84     pub unsafe fn clone_from_ptr(ptr: NonNull<ffi::AConfiguration>) -> Self {
85         let conf = Self::new();
86         ffi::AConfiguration_copy(conf.ptr.as_ptr(), ptr.as_ptr());
87         conf
88     }
89 
90     /// The pointer to the native `AConfiguration`.  Keep in mind that the `Configuration` object
91     /// still has ownership, and will free it when dropped.
92     pub fn ptr(&self) -> NonNull<ffi::AConfiguration> {
93         self.ptr
94     }
95 
96     pub fn from_asset_manager(am: &AssetManager) -> Self {
97         let config = Self::new();
98         unsafe {
99             ffi::AConfiguration_fromAssetManager(config.ptr().as_mut(), am.ptr().as_mut());
100         }
101         config
102     }
103 
104     /// Create a new `Configuration`, with none of the values set.
105     pub fn new() -> Self {
106         unsafe {
107             Self {
108                 ptr: NonNull::new(ffi::AConfiguration_new()).unwrap(),
109             }
110         }
111     }
112 
113     /// `dest.copy(&src)` copies the contents of `src` to `dest`
114     pub fn copy(&mut self, other: &Self) {
115         unsafe { ffi::AConfiguration_copy(self.ptr.as_ptr(), other.ptr.as_ptr()) }
116     }
117 
118     /// Information about what fields differ between the two configurations
119     pub fn diff(&self, other: &Self) -> DiffResult {
120         unsafe {
121             DiffResult(ffi::AConfiguration_diff(self.ptr.as_ptr(), other.ptr.as_ptr()) as u32)
122         }
123     }
124 
125     /// Returns false if anything in `self` conflicts with `requested`
126     pub fn matches(&self, requested: &Self) -> bool {
127         unsafe { ffi::AConfiguration_match(self.ptr.as_ptr(), requested.ptr.as_ptr()) != 0 }
128     }
129 
130     /// Returns the country code. It will always be two letters.
131     pub fn country(&self) -> String {
132         let mut result = "  ".to_owned();
133         unsafe {
134             ffi::AConfiguration_getCountry(self.ptr.as_ptr(), result.as_mut_ptr() as *mut _);
135         }
136         result
137     }
138 
139     /// Returns the screen density in dpi.
140     ///
141     /// On some devices it can return values outside of the density enum.
142     pub fn density(&self) -> Option<u32> {
143         let density = unsafe { ffi::AConfiguration_getDensity(self.ptr.as_ptr()) as u32 };
144         match density {
145             ffi::ACONFIGURATION_DENSITY_DEFAULT => Some(160),
146             ffi::ACONFIGURATION_DENSITY_ANY => None,
147             ffi::ACONFIGURATION_DENSITY_NONE => None,
148             density => Some(density),
149         }
150     }
151 
152     /// Returns the keyboard type.
153     pub fn keyboard(&self) -> Keyboard {
154         unsafe {
155             (ffi::AConfiguration_getKeyboard(self.ptr.as_ptr()) as u32)
156                 .try_into()
157                 .unwrap()
158         }
159     }
160 
161     /// Returns keyboard visibility/availability.
162     pub fn keys_hidden(&self) -> KeysHidden {
163         unsafe {
164             (ffi::AConfiguration_getKeysHidden(self.ptr.as_ptr()) as u32)
165                 .try_into()
166                 .unwrap()
167         }
168     }
169 
170     /// Returns the language, as a `String` of two characters, if a language is set
171     pub fn language(&self) -> Option<String> {
172         let mut chars = [0u8; 2];
173         unsafe {
174             ffi::AConfiguration_getLanguage(self.ptr.as_ptr(), chars[..].as_mut_ptr() as *mut _);
175         }
176         if chars[0] == 0 {
177             None
178         } else {
179             Some(std::str::from_utf8(&chars[..]).unwrap().to_owned())
180         }
181     }
182 
183     /// Returns the layout direction
184     pub fn layout_direction(&self) -> LayoutDir {
185         unsafe {
186             (ffi::AConfiguration_getLayoutDirection(self.ptr.as_ptr()) as u32)
187                 .try_into()
188                 .unwrap()
189         }
190     }
191 
192     /// Returns the mobile country code.
193     pub fn mcc(&self) -> i32 {
194         unsafe { ffi::AConfiguration_getMcc(self.ptr.as_ptr()) }
195     }
196 
197     /// Returns the mobile network code, if one is defined
198     pub fn mnc(&self) -> Option<i32> {
199         unsafe {
200             match ffi::AConfiguration_getMnc(self.ptr.as_ptr()) {
201                 0 => None,
202                 x if x == ffi::ACONFIGURATION_MNC_ZERO as i32 => Some(0),
203                 x => Some(x),
204             }
205         }
206     }
207 
208     pub fn nav_hidden(&self) -> NavHidden {
209         unsafe {
210             (ffi::AConfiguration_getNavHidden(self.ptr.as_ptr()) as u32)
211                 .try_into()
212                 .unwrap()
213         }
214     }
215 
216     pub fn navigation(&self) -> Navigation {
217         unsafe {
218             (ffi::AConfiguration_getNavigation(self.ptr.as_ptr()) as u32)
219                 .try_into()
220                 .unwrap()
221         }
222     }
223 
224     pub fn orientation(&self) -> Orientation {
225         unsafe {
226             (ffi::AConfiguration_getOrientation(self.ptr.as_ptr()) as u32)
227                 .try_into()
228                 .unwrap()
229         }
230     }
231 
232     pub fn screen_height_dp(&self) -> Option<i32> {
233         unsafe {
234             let height = ffi::AConfiguration_getScreenHeightDp(self.ptr.as_ptr());
235             if height == ffi::ACONFIGURATION_SCREEN_HEIGHT_DP_ANY as i32 {
236                 None
237             } else {
238                 Some(height)
239             }
240         }
241     }
242 
243     pub fn screen_width_dp(&self) -> Option<i32> {
244         unsafe {
245             let width = ffi::AConfiguration_getScreenWidthDp(self.ptr.as_ptr());
246             if width == ffi::ACONFIGURATION_SCREEN_WIDTH_DP_ANY as i32 {
247                 None
248             } else {
249                 Some(width)
250             }
251         }
252     }
253 
254     pub fn screen_long(&self) -> ScreenLong {
255         unsafe {
256             (ffi::AConfiguration_getScreenLong(self.ptr.as_ptr()) as u32)
257                 .try_into()
258                 .unwrap()
259         }
260     }
261 
262     pub fn screen_round(&self) -> ScreenRound {
263         unsafe {
264             (ffi::AConfiguration_getScreenRound(self.ptr.as_ptr()) as u32)
265                 .try_into()
266                 .unwrap()
267         }
268     }
269 
270     pub fn screen_size(&self) -> ScreenSize {
271         unsafe {
272             (ffi::AConfiguration_getScreenSize(self.ptr.as_ptr()) as u32)
273                 .try_into()
274                 .unwrap()
275         }
276     }
277 
278     pub fn sdk_version(&self) -> i32 {
279         unsafe { ffi::AConfiguration_getSdkVersion(self.ptr.as_ptr()) }
280     }
281 
282     pub fn smallest_screen_width_dp(&self) -> Option<i32> {
283         unsafe {
284             let width = ffi::AConfiguration_getSmallestScreenWidthDp(self.ptr.as_ptr());
285             if width == ffi::ACONFIGURATION_SMALLEST_SCREEN_WIDTH_DP_ANY as i32 {
286                 None
287             } else {
288                 Some(width)
289             }
290         }
291     }
292 
293     pub fn touchscreen(&self) -> Touchscreen {
294         unsafe {
295             (ffi::AConfiguration_getTouchscreen(self.ptr.as_ptr()) as u32)
296                 .try_into()
297                 .unwrap()
298         }
299     }
300 
301     pub fn ui_mode_night(&self) -> UiModeNight {
302         unsafe {
303             (ffi::AConfiguration_getUiModeNight(self.ptr.as_ptr()) as u32)
304                 .try_into()
305                 .unwrap()
306         }
307     }
308 
309     pub fn ui_mode_type(&self) -> UiModeType {
310         unsafe {
311             (ffi::AConfiguration_getUiModeType(self.ptr.as_ptr()) as u32)
312                 .try_into()
313                 .unwrap()
314         }
315     }
316 }
317 
318 /// A bitfield representing the differences between two `Configuration`s
319 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
320 pub struct DiffResult(pub u32);
321 
322 impl DiffResult {
323     pub fn mcc(self) -> bool {
324         self.0 & ffi::ACONFIGURATION_MCC != 0
325     }
326     pub fn mnc(self) -> bool {
327         self.0 & ffi::ACONFIGURATION_MNC != 0
328     }
329     pub fn locale(self) -> bool {
330         self.0 & ffi::ACONFIGURATION_LOCALE != 0
331     }
332     pub fn touchscreen(self) -> bool {
333         self.0 & ffi::ACONFIGURATION_TOUCHSCREEN != 0
334     }
335     pub fn keyboard(self) -> bool {
336         self.0 & ffi::ACONFIGURATION_KEYBOARD != 0
337     }
338     pub fn keyboard_hidden(self) -> bool {
339         self.0 & ffi::ACONFIGURATION_KEYBOARD_HIDDEN != 0
340     }
341     pub fn navigation(self) -> bool {
342         self.0 & ffi::ACONFIGURATION_NAVIGATION != 0
343     }
344     pub fn orientation(self) -> bool {
345         self.0 & ffi::ACONFIGURATION_ORIENTATION != 0
346     }
347     pub fn density(self) -> bool {
348         self.0 & ffi::ACONFIGURATION_DENSITY != 0
349     }
350     pub fn screen_size(self) -> bool {
351         self.0 & ffi::ACONFIGURATION_SCREEN_SIZE != 0
352     }
353     pub fn version(self) -> bool {
354         self.0 & ffi::ACONFIGURATION_VERSION != 0
355     }
356     pub fn screen_layout(self) -> bool {
357         self.0 & ffi::ACONFIGURATION_SCREEN_LAYOUT != 0
358     }
359     pub fn ui_mode(self) -> bool {
360         self.0 & ffi::ACONFIGURATION_UI_MODE != 0
361     }
362     pub fn smallest_screen_size(self) -> bool {
363         self.0 & ffi::ACONFIGURATION_SMALLEST_SCREEN_SIZE != 0
364     }
365     pub fn layout_dir(self) -> bool {
366         self.0 & ffi::ACONFIGURATION_LAYOUTDIR != 0
367     }
368     pub fn screen_round(self) -> bool {
369         self.0 & ffi::ACONFIGURATION_SCREEN_ROUND != 0
370     }
371     pub fn color_mode(self) -> bool {
372         self.0 & ffi::ACONFIGURATION_COLOR_MODE != 0
373     }
374 }
375 
376 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
377 #[repr(u32)]
378 pub enum Orientation {
379     Any = ffi::ACONFIGURATION_ORIENTATION_ANY,
380     Port = ffi::ACONFIGURATION_ORIENTATION_PORT,
381     Land = ffi::ACONFIGURATION_ORIENTATION_LAND,
382     Square = ffi::ACONFIGURATION_ORIENTATION_SQUARE,
383 }
384 
385 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
386 #[repr(u32)]
387 pub enum Touchscreen {
388     Any = ffi::ACONFIGURATION_TOUCHSCREEN_ANY,
389     NoTouch = ffi::ACONFIGURATION_TOUCHSCREEN_NOTOUCH,
390     Stylus = ffi::ACONFIGURATION_TOUCHSCREEN_STYLUS,
391     Finger = ffi::ACONFIGURATION_TOUCHSCREEN_FINGER,
392 }
393 
394 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
395 #[repr(u32)]
396 pub enum Density {
397     Default = ffi::ACONFIGURATION_DENSITY_DEFAULT,
398     Low = ffi::ACONFIGURATION_DENSITY_LOW,
399     Medium = ffi::ACONFIGURATION_DENSITY_MEDIUM,
400     TV = ffi::ACONFIGURATION_DENSITY_TV,
401     High = ffi::ACONFIGURATION_DENSITY_HIGH,
402     XHigh = ffi::ACONFIGURATION_DENSITY_XHIGH,
403     XXHigh = ffi::ACONFIGURATION_DENSITY_XXHIGH,
404     XXXHigh = ffi::ACONFIGURATION_DENSITY_XXXHIGH,
405     Any = ffi::ACONFIGURATION_DENSITY_ANY,
406     None = ffi::ACONFIGURATION_DENSITY_NONE,
407 }
408 
409 impl Density {
410     /// The DPI associated with the density class.
411     /// See [the Android screen density
412     /// docs](https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp)
413     ///
414     /// There are some `Density` values that have no associated DPI; these values return `None`.
415     pub fn dpi(self) -> Option<u32> {
416         match self {
417             Self::Default => Some(160), // Or should it be None?
418             Self::Low => Some(120),
419             Self::Medium => Some(160),
420             Self::High => Some(240),
421             Self::XHigh => Some(320),
422             Self::XXHigh => Some(480),
423             Self::XXXHigh => Some(640),
424             Self::TV => Some(213),
425             Self::Any => None,
426             Self::None => None,
427         }
428     }
429 
430     /// The Hi-DPI factor associated with the density class.  This is the factor by which an
431     /// image/resource should be scaled to match its size across devices.  The baseline is a 160dpi
432     /// screen (i.e., Hi-DPI factor = DPI / 160).
433     /// See [the Android screen density
434     /// docs](https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp)
435     ///
436     /// There are some `Density` values that have no associated DPI; these values return `None`.
437     pub fn approx_hidpi_factor(self) -> Option<f64> {
438         match self {
439             Self::Default => Some(1.), // Or should it be None?
440             Self::Low => Some(0.75),
441             Self::Medium => Some(1.),
442             Self::High => Some(1.5),
443             Self::XHigh => Some(2.),
444             Self::XXHigh => Some(3.),
445             Self::XXXHigh => Some(4.),
446             Self::TV => Some(4. / 3.),
447             Self::Any => None,
448             Self::None => None,
449         }
450     }
451 }
452 
453 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
454 #[repr(u32)]
455 pub enum Keyboard {
456     Any = ffi::ACONFIGURATION_KEYBOARD_ANY,
457     NoKeys = ffi::ACONFIGURATION_KEYBOARD_NOKEYS,
458     Qwerty = ffi::ACONFIGURATION_KEYBOARD_QWERTY,
459     TwelveKey = ffi::ACONFIGURATION_KEYBOARD_12KEY,
460 }
461 
462 // FIXME is it a bitmask?
463 // FIXME are they all bitmasks?
464 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
465 #[repr(u32)]
466 pub enum Navigation {
467     Any = ffi::ACONFIGURATION_NAVIGATION_ANY,
468     NoNav = ffi::ACONFIGURATION_NAVIGATION_NONAV,
469     DPad = ffi::ACONFIGURATION_NAVIGATION_DPAD,
470     Trackball = ffi::ACONFIGURATION_NAVIGATION_TRACKBALL,
471     Wheel = ffi::ACONFIGURATION_NAVIGATION_WHEEL,
472 }
473 
474 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
475 #[repr(u32)]
476 pub enum KeysHidden {
477     Any = ffi::ACONFIGURATION_KEYSHIDDEN_ANY,
478     No = ffi::ACONFIGURATION_KEYSHIDDEN_NO,
479     Yes = ffi::ACONFIGURATION_KEYSHIDDEN_YES,
480     Soft = ffi::ACONFIGURATION_KEYSHIDDEN_SOFT,
481 }
482 
483 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
484 #[repr(u32)]
485 pub enum NavHidden {
486     Any = ffi::ACONFIGURATION_NAVHIDDEN_ANY,
487     No = ffi::ACONFIGURATION_NAVHIDDEN_NO,
488     Yes = ffi::ACONFIGURATION_NAVHIDDEN_YES,
489 }
490 
491 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
492 #[repr(u32)]
493 pub enum ScreenSize {
494     Any = ffi::ACONFIGURATION_SCREENSIZE_ANY,
495     Small = ffi::ACONFIGURATION_SCREENSIZE_SMALL,
496     Normal = ffi::ACONFIGURATION_SCREENSIZE_NORMAL,
497     Large = ffi::ACONFIGURATION_SCREENSIZE_LARGE,
498     XLarge = ffi::ACONFIGURATION_SCREENSIZE_XLARGE,
499 }
500 
501 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
502 #[repr(u32)]
503 pub enum ScreenLong {
504     Any = ffi::ACONFIGURATION_SCREENLONG_ANY,
505     No = ffi::ACONFIGURATION_SCREENLONG_NO,
506     Yes = ffi::ACONFIGURATION_SCREENLONG_YES,
507 }
508 
509 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
510 #[repr(u32)]
511 pub enum ScreenRound {
512     Any = ffi::ACONFIGURATION_SCREENROUND_ANY,
513     No = ffi::ACONFIGURATION_SCREENROUND_NO,
514     Yes = ffi::ACONFIGURATION_SCREENROUND_YES,
515 }
516 
517 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
518 #[repr(u32)]
519 pub enum WideColorGamut {
520     Any = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_ANY,
521     No = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_NO,
522     Yes = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_YES,
523 }
524 
525 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
526 #[repr(u32)]
527 pub enum HDR {
528     Any = ffi::ACONFIGURATION_HDR_ANY,
529     No = ffi::ACONFIGURATION_HDR_NO,
530     Yes = ffi::ACONFIGURATION_HDR_YES,
531 }
532 
533 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
534 #[repr(u32)]
535 pub enum LayoutDir {
536     Any = ffi::ACONFIGURATION_LAYOUTDIR_ANY,
537     Ltr = ffi::ACONFIGURATION_LAYOUTDIR_LTR,
538     Rtl = ffi::ACONFIGURATION_LAYOUTDIR_RTL,
539 }
540 
541 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
542 #[repr(u32)]
543 pub enum UiModeType {
544     Any = ffi::ACONFIGURATION_UI_MODE_TYPE_ANY,
545     Normal = ffi::ACONFIGURATION_UI_MODE_TYPE_NORMAL,
546     Desk = ffi::ACONFIGURATION_UI_MODE_TYPE_DESK,
547     Car = ffi::ACONFIGURATION_UI_MODE_TYPE_CAR,
548     Television = ffi::ACONFIGURATION_UI_MODE_TYPE_TELEVISION,
549     Applicance = ffi::ACONFIGURATION_UI_MODE_TYPE_APPLIANCE,
550     Watch = ffi::ACONFIGURATION_UI_MODE_TYPE_WATCH,
551     VrHeadset = ffi::ACONFIGURATION_UI_MODE_TYPE_VR_HEADSET,
552 }
553 
554 #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
555 #[repr(u32)]
556 pub enum UiModeNight {
557     Any = ffi::ACONFIGURATION_UI_MODE_NIGHT_ANY,
558     No = ffi::ACONFIGURATION_UI_MODE_NIGHT_NO,
559     Yes = ffi::ACONFIGURATION_UI_MODE_NIGHT_YES,
560 }
561