io.github.mngsk.devicedetector.Detection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of device-detector Show documentation
Show all versions of device-detector Show documentation
The Universal Device Detection library that parses User Agents and detects devices (desktop, tablet, mobile, tv, cars, console, etc.), clients (browsers, feed readers, media players, PIMs, ...), operating systems, brands and models.
package io.github.mngsk.devicedetector;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import org.apache.maven.artifact.versioning.ComparableVersion;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.mngsk.devicedetector.client.Client;
import io.github.mngsk.devicedetector.client.browser.Browser;
import io.github.mngsk.devicedetector.device.Device;
import io.github.mngsk.devicedetector.operatingsystem.OperatingSystem;
import io.github.mngsk.devicedetector.operatingsystem.VendorFragmentParser;
public class Detection {
private static VendorFragmentParser vendorFragmentParser = new VendorFragmentParser();
private static Pattern touchEnabledPattern = Pattern
.compile("(?:^|[^A-Z_-])(?:Touch)");
private static Pattern androidTabletPattern = Pattern.compile(
"(?:^|[^A-Z_-])(?:Android( [\\.0-9]+)?; Tablet;)",
Pattern.CASE_INSENSITIVE);
private static Pattern androidMobilePattern = Pattern.compile(
"(?:^|[^A-Z_-])(?:Android( [\\.0-9]+)?; Mobile;)",
Pattern.CASE_INSENSITIVE);
private static Pattern chromeSmartphonePattern = Pattern.compile(
"(?:^|[^A-Z_-])(?:Chrome/[\\.0-9]* Mobile)",
Pattern.CASE_INSENSITIVE);
private static Pattern chromeTabletPattern = Pattern.compile(
"(?:^|[^A-Z_-])(?:Chrome/[\\.0-9]* (?!Mobile))",
Pattern.CASE_INSENSITIVE);
private static Pattern operaTabletPattern = Pattern.compile(
"(?:^|[^A-Z_-])(?:Opera Tablet)", Pattern.CASE_INSENSITIVE);
private static Pattern operaTvPattern = Pattern.compile(
"(?:^|[^A-Z_-])(?:Opera TV Store)", Pattern.CASE_INSENSITIVE);
private static List mobileDeviceTypes = Arrays.asList(
"feature phone", "smartphone", "tablet", "phablet", "camera",
"portable media player");
private static List nonMobileDeviceTypes = Arrays.asList("tv",
"smart display", "console");
private static List mobileOnlyBrowsers = Arrays.asList(
"360 Phone Browser", "Oculus Browser", "Puffin", "Skyfire",
"Mobile Safari", "Opera Mini", "Opera Mobile",
"DuckDuckGo Privacy Browser", "dbrowser", "Streamy", "B-Line",
"Isivioo", "Firefox Mobile", "Coast", "Aloha Browser",
"Sailfish Browser", "Samsung Browser", "Firefox Rocket",
"Web Explorer", "Hawk Turbo Browser", "Nox Browser",
"Huawei Browser", "vivo Browser", "Realme Browser", "COS Browser",
"Meizu Browser", "UC Browser Mini", "Firefox Focus", "Faux Browser",
"Wear Internet Browser", "Minimo", "mCent", "Aloha Browser Lite",
"Super Fast Browser", "EUI Browser", "eZ Browser",
"UC Browser Turbo", "Delta Browser", "START Internet Browser");
private static List desktopOperatingSystems = Arrays.asList(
"AmigaOS", "IBM", "GNU/Linux", "Mac", "Unix", "Windows", "BeOS",
"Chrome OS");
private static List appleOperatingSystems = Arrays
.asList("Apple TV", "iOS", "Mac");
private static List tvBrowsers = Arrays.asList("Kylo",
"Espial TV Browser");
private static ComparableVersion v2 = new ComparableVersion("2.0");
private static ComparableVersion v3 = new ComparableVersion("3.0");
private static ComparableVersion v4 = new ComparableVersion("4.0");
private static ComparableVersion v8 = new ComparableVersion("8");
private final String userAgent;
private final Device device;
private final OperatingSystem operatingSystem;
private final Client client;
public Detection(String userAgent, Device device,
OperatingSystem operatingSystem, Client client) {
this.userAgent = userAgent;
this.operatingSystem = operatingSystem;
this.client = client;
this.device = fillDevice(device);
}
private Device fillDevice(Device device) {
String type = null;
String brand = null;
String model = null;
if (device != null) {
type = device.getType();
brand = device.getBrand().orElse(null);
model = device.getModel().orElse(null);
}
if (brand == null) {
brand = vendorFragmentParser.parse(this.userAgent).orElse(null);
}
// Assume all devices running iOS / Mac OS are from Apple
if (brand == null) {
if (this.operatingSystem != null && appleOperatingSystems
.contains(this.operatingSystem.getName())) {
brand = "Apple";
}
}
// Chrome on Android passes the device type based on the keyword
// 'Mobile'. If it is present the device should be a smartphone,
// otherwise it's a tablet. See
// https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent
if (type == null && this.operatingSystem != null
&& this.operatingSystem.getFamily().orElse("").equals("Android")
&& this.client != null
&& this.client.getType().equals("browser")
&& ((Browser) this.client).getFamily().isPresent()
&& ((Browser) this.client).getFamily().get().equals("Chrome")) {
if (chromeSmartphonePattern.matcher(userAgent).find()) {
type = "smartphone";
} else if (chromeTabletPattern.matcher(userAgent).find()) {
type = "tablet";
}
}
// Some user agents simply contain the fragment 'Android; Tablet;' or
// 'Opera Tablet', so we assume those devices as tablets
if (type == null && (androidTabletPattern.matcher(userAgent).find()
|| operaTabletPattern.matcher(userAgent).find())) {
type = "tablet";
}
// Some user agents simply contain the fragment 'Android; Mobile;', so
// we assume those devices as smartphones
if (type == null && androidMobilePattern.matcher(userAgent).find()) {
type = "smartphone";
}
Optional osVersion = Optional.empty();
if (this.operatingSystem != null
&& !this.operatingSystem.getVersion().orElse("").isEmpty()) {
osVersion = Optional.of(new ComparableVersion(
this.operatingSystem.getVersion().get()));
}
// Android up to 3.0 was designed for smartphones only. But as 3.0,
// which was tablet only, was published too late, there were a bunch of
// tablets running with 2.x.
// With 4.0 the two trees were merged and it is for smartphones and
// tablets.
// So were are expecting that all devices running Android < 2 are
// smartphones. Devices running Android 3.X are tablets. Device type of
// Android 2.X and 4.X+ are unknown
if (type == null && this.operatingSystem != null
&& this.operatingSystem.getFamily().orElse("").equals("Android")
&& osVersion.isPresent()) {
if (osVersion.get().compareTo(v2) < 0) {
type = "smartphone";
} else if (osVersion.get().compareTo(v3) >= 0
&& osVersion.get().compareTo(v4) < 0) {
type = "tablet";
}
}
// All detected feature phones running android are more likely a
// smartphone
if (type != null && type.equals("feature phone")
&& this.operatingSystem != null && this.operatingSystem
.getFamily().orElse("").equals("Android")) {
type = "smartphone";
}
// According to
// http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
// Internet Explorer 10 introduces the "Touch" UA string token. If this
// token is present at the end of the UA string, the computer has touch
// capability, and is running Windows 8 (or later).
// This UA string will be transmitted on a touch-enabled system running
// Windows 8 (RT)
// As most touch enabled devices are tablets and only a smaller part are
// desktops/notebooks we assume that all Windows 8 touch devices are
// tablets.
if (type == null && this.operatingSystem != null
&& (this.operatingSystem.getName().equals("Windows RT")
|| (this.operatingSystem.getName().equals("Windows")
&& osVersion.isPresent()
&& osVersion.get().compareTo(v8) >= 0))
&& touchEnabledPattern.matcher(userAgent).find()) {
type = "tablet";
}
// All devices running Opera TV Store are assumed to be a tv
if (operaTvPattern.matcher(userAgent).find()) {
type = "tv";
}
// Devices running Kylo or Espital TV Browsers are assumed to be a TV
if (type == null && this.client != null
&& tvBrowsers.contains(this.client.getName().orElse(""))) {
type = "tv";
}
// set device type to desktop for all devices running a desktop os that
// were not detected as an other device type
if (type == null && isDesktop()) {
type = "desktop";
}
if (type != null) {
return new Device(type, brand, model);
} else {
return null;
}
}
@JsonIgnore
public String getUserAgent() {
return this.userAgent;
}
public Optional getDevice() {
return Optional.ofNullable(this.device);
}
public Optional getOperatingSystem() {
return Optional.ofNullable(this.operatingSystem);
}
public Optional getClient() {
return Optional.ofNullable(this.client);
}
@JsonIgnore
public boolean isTouchEnabled() {
return touchEnabledPattern.matcher(userAgent).find();
}
@JsonIgnore
public boolean isSmartphone() {
return this.device != null ? this.device.getType().equals("smartphone")
: false;
}
@JsonIgnore
public boolean isFeaturePhone() {
return this.device != null
? this.device.getType().equals("feature phone") : false;
}
@JsonIgnore
public boolean isTablet() {
return this.device != null ? this.device.getType().equals("tablet")
: false;
}
@JsonIgnore
public boolean isPhablet() {
return this.device != null ? this.device.getType().equals("phablet")
: false;
}
@JsonIgnore
public boolean isConsole() {
return this.device != null ? this.device.getType().equals("console")
: false;
}
@JsonIgnore
public boolean isPortableMediaPlayer() {
return this.device != null
? this.device.getType().equals("portable media player") : false;
}
@JsonIgnore
public boolean isCarBrowser() {
return this.device != null ? this.device.getType().equals("car browser")
: false;
}
@JsonIgnore
public boolean isCamera() {
return this.device != null ? this.device.getType().equals("camera")
: false;
}
@JsonIgnore
public boolean isTV() {
return this.device != null ? this.device.getType().equals("tv") : false;
}
@JsonIgnore
public boolean isSmartDisplay() {
return this.device != null
? this.device.getType().equals("smart display") : false;
}
@JsonIgnore
public boolean isSmartSpeaker() {
return this.device != null
? this.device.getType().equals("smart speaker") : false;
}
@JsonIgnore
public boolean usesMobileBrowser() {
return this.client != null && this.client.getType().equals("browser")
&& mobileOnlyBrowsers
.contains(this.client.getName().orElse(""));
}
@JsonIgnore
public boolean isMobile() {
if (this.device != null
&& mobileDeviceTypes.contains(this.device.getType())) {
return true;
}
if (this.device != null
&& nonMobileDeviceTypes.contains(this.device.getType())) {
return false;
}
if (usesMobileBrowser()) {
return true;
}
if (this.operatingSystem == null) {
return false;
}
return !isBot() && !isDesktop();
}
@JsonIgnore
public boolean isDesktop() {
if (this.operatingSystem == null) {
return false;
}
if (usesMobileBrowser()) {
return false;
}
return desktopOperatingSystems
.contains(this.operatingSystem.getFamily().orElse(""));
}
@JsonIgnore
public boolean isBot() {
return this.client != null ? this.client.getType().equals("bot")
: false;
}
@JsonIgnore
public boolean isBrowser() {
return this.client != null ? this.client.getType().equals("browser")
: false;
}
@JsonIgnore
public boolean isFeedReader() {
return this.client != null ? this.client.getType().equals("feed reader")
: false;
}
@JsonIgnore
public boolean isMobileApp() {
return this.client != null ? this.client.getType().equals("mobile app")
: false;
}
@JsonIgnore
public boolean isPIM() {
return this.client != null ? this.client.getType().equals("pim")
: false;
}
@JsonIgnore
public boolean isLibrary() {
return this.client != null ? this.client.getType().equals("library")
: false;
}
@JsonIgnore
public boolean isMediaPlayer() {
return this.client != null ? this.client.getType().equals("mediaplayer")
: false;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (this.device != null) {
builder.append(this.device);
}
if (this.operatingSystem != null) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(this.operatingSystem);
}
if (this.client != null) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(this.client);
}
if (builder.length() == 0) {
builder.append("unknown");
}
return builder.toString();
}
}