[JAVA] Determine the device type of smartphones, tablets, and PCs with Spring Mobile

Overview

--Use Spring Boot + Spring Mobile to determine whether the device you are accessing is a smartphone, tablet, or computer.

Source code

Source code list

Only two, DeviceInfoController.java and pom.xml.

├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── example
                    └── deviceinfo
                        └── DeviceInfoController.java

Maven build file pom.xml

-Introduced Spring Mobile in dependencies --Added repository with milestone version of Spring Mobile to repositories (because the version of Spring Mobile used this time is 2.0.0.M3)

pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <groupId>com.example</groupId>
  <artifactId>deviceinfo</artifactId>
  <version>0.0.1</version>
  <name>deviceinfo</name>
  <description>Deviceinfo project for Spring Boot</description>

  <properties>
    <java.version>11</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--Introduced Spring Mobile-->
    <dependency>
      <groupId>org.springframework.mobile</groupId>
      <artifactId>spring-mobile-device</artifactId>
      <version>2.0.0.M3</version>
    </dependency>
  </dependencies>

  <repositories>
    <!--Added repository with milestone version of Spring Mobile-->
    <repository>
      <id>spring-milestone</id>
      <name>Spring Milestone Repository</name>
      <url>http://repo.spring.io/milestone</url>
    </repository>
  </repositories>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

DeviceInfoController.java

package com.example.deviceinfo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.mobile.device.Device;
import org.springframework.mobile.device.DevicePlatform;
import org.springframework.mobile.device.DeviceResolverHandlerInterceptor;
import org.springframework.mobile.device.DeviceUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
@Configuration
@RestController
public class DeviceInfoController implements WebMvcConfigurer {

  public static void main(String[] args) {
    SpringApplication.run(DeviceInfoController.class, args);
  }

  //Required to use Spring Mobile
  @Bean
  public DeviceResolverHandlerInterceptor deviceResolverHandlerInterceptor() {
    return new DeviceResolverHandlerInterceptor();
  }

  //Required to use Spring Mobile
  // WebMvcConfigurer#addInterceptors
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(deviceResolverHandlerInterceptor());
  }

  @RequestMapping("/")
  public Map index(WebRequest req) {

    Map res = new HashMap<String, String>();

    String userAgent = req.getHeader(HttpHeaders.USER_AGENT);
    res.put("useragent", userAgent);

    Device device = DeviceUtils.getCurrentDevice(req);
    if (device == null) {
      res.put("device", "null");
    } else if (device.isMobile()) {
      res.put("device", "mobile");
    } else if (device.isTablet()) {
      res.put("device", "tablet");
    } else if (device.isNormal()) {
      res.put("device", "normal");
    }

    if (device != null) {
      DevicePlatform dp = device.getDevicePlatform();
      switch (dp) {
        case ANDROID:
          res.put("platform", "android");
          break;
        case IOS:
          res.put("platform", "ios");
          break;
        case UNKNOWN:
          res.put("platform", "unknown");
          break;
      }
    }

    return res;
  }
}

Build and launch

Generate a JAR file with mvn package.

$ mvn package

Start Spring Boot with java command.

$ java -jar target/deviceinfo-0.0.1.jar

Try to access with various user agents

Try to access by specifying various user agents with Ruby + curl.

require 'json'

ualist = []

## Smartphone

# iPhone + iOS + Safari
ualist << 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1'

# iPhone + iOS + Chrome
ualist << 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/69.0.3497.91 Mobile/15E148 Safari/605.1'

# Galaxy A30 SCV43 + Android + Browser
ualist << 'Mozilla/5.0 (Linux; Android 9; SCV43 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.0 Chrome/67.0.3396.87 Mobile Safari/537.36'

# Xperia XZ3 SOV39 + Chrome
ualist << 'Mozilla/5.0 (Linux; Android 9; SOV39 Build/52.0.C.1.119) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36'

## Tablet

# iPad mini + iOS + Safari
ualist << 'Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1'

# Qua tab QZ10 + Android + Chrome
ualist << 'Mozilla/5.0 (Linux; Android 8.1.0; KYT33 Build/3.020VE.0072.a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Safari/537.36'

## Desktop

# macOS + Safari
ualist << 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15'

# Windows 10 + Microsoft Edge
ualist << 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362'

ualist.each do |ua|
  json = `#{"curl --silent --user-agent '#{ua}' http://localhost:8080/"}`
  puts JSON.pretty_generate(JSON.parse(json))
  puts
end

Execution result. You can see that the user agent has been analyzed and can be clearly identified.

{
  "useragent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1",
  "device": "mobile",
  "platform": "ios"
}

{
  "useragent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/69.0.3497.91 Mobile/15E148 Safari/605.1",
  "device": "mobile",
  "platform": "ios"
}

{
  "useragent": "Mozilla/5.0 (Linux; Android 9; SCV43 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.0 Chrome/67.0.3396.87 Mobile Safari/537.36",
  "device": "mobile",
  "platform": "android"
}

{
  "useragent": "Mozilla/5.0 (Linux; Android 9; SOV39 Build/52.0.C.1.119) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36",
  "device": "mobile",
  "platform": "android"
}

{
  "useragent": "Mozilla/5.0 (iPad; CPU OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1",
  "device": "tablet",
  "platform": "ios"
}

{
  "useragent": "Mozilla/5.0 (Linux; Android 8.1.0; KYT33 Build/3.020VE.0072.a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Safari/537.36",
  "device": "tablet",
  "platform": "android"
}

{
  "useragent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15",
  "device": "normal",
  "platform": "unknown"
}

{
  "useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362",
  "device": "normal",
  "platform": "unknown"
}

Allow Device to be specified as an argument of the handler method

Modify DeviceInfoController.java so that Device can be specified as an argument of the handler method (method with @RequestMapping annotation).

package com.example.deviceinfo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.mobile.device.Device;
import org.springframework.mobile.device.DeviceHandlerMethodArgumentResolver;
import org.springframework.mobile.device.DevicePlatform;
import org.springframework.mobile.device.DeviceResolverHandlerInterceptor;
import org.springframework.mobile.device.DeviceUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootApplication
@Configuration
@RestController
public class DeviceInfoController implements WebMvcConfigurer {

  public static void main(String[] args) {
    SpringApplication.run(DeviceInfoController.class, args);
  }

  //Required to use Spring Mobile
  @Bean
  public DeviceResolverHandlerInterceptor deviceResolverHandlerInterceptor() {
    return new DeviceResolverHandlerInterceptor();
  }

  //Required to use Spring Mobile
  // WebMvcConfigurer#addInterceptors
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(deviceResolverHandlerInterceptor());
  }

  // @Method with RequestMapping(Handler method)To the argument of
  //Required to be able to specify the Device of Spring Mobile
  @Bean
  public DeviceHandlerMethodArgumentResolver deviceHandlerMethodArgumentResolver() {
    return new DeviceHandlerMethodArgumentResolver();
  }

  // @Method with RequestMapping(Handler method)To the argument of
  //Required to be able to specify the Device of Spring Mobile
  // WebMvcConfigurer#addArgumentResolvers
  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(deviceHandlerMethodArgumentResolver());
  }

  @RequestMapping("/")
  public Map index(WebRequest req, Device device) {

    Map res = new HashMap<String, String>();

    String userAgent = req.getHeader(HttpHeaders.USER_AGENT);
    res.put("useragent", userAgent);

    //This process is unnecessary because Device can be specified as an argument of the handler method.
    //Device device = DeviceUtils.getCurrentDevice(req);

    if (device == null) {
      res.put("device", "null");
    } else if (device.isMobile()) {
      res.put("device", "mobile");
    } else if (device.isTablet()) {
      res.put("device", "tablet");
    } else if (device.isNormal()) {
      res.put("device", "normal");
    }

    if (device != null) {
      DevicePlatform dp = device.getDevicePlatform();
      switch (dp) {
        case ANDROID:
          res.put("platform", "android");
          break;
        case IOS:
          res.put("platform", "ios");
          break;
        case UNKNOWN:
          res.put("platform", "unknown");
          break;
      }
    }

    return res;
  }
}

How Spring Mobile identifies devices

The next article is about an older version, but I suspect it hasn't changed much.

Spring Mobile 1 \ .0 released

LiteDeviceResolver is used by default for device resolution. It is based on the WordPress Mobile Pack detection algorithm. Another DeviceResolver implementation can be plugged in by injecting the constructor argument of DeviceResolverHandlerInterceptor. Resolvers for more sophisticated devices like WURFL can identify specific device features such as screen size, manufacturer, model, or priority markup.

If you look at the source code of LiteDeviceResolver.java, you can see how the device is identified from the user agent string.

spring-mobile/LiteDeviceResolver.java at v2.0.0.M3 · spring-projects/spring-mobile · GitHub

// Android special case
if (userAgent.contains("android")) {
  return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.ANDROID);
}
// Apple special case
if (userAgent.contains("iphone") || userAgent.contains("ipod") || userAgent.contains("ipad")) {
  return resolveWithPlatform(DeviceType.MOBILE, DevicePlatform.IOS);
}

In order to use Spring Mobile, you need to implement WebMvcConfigurer # addInterceptors and add the DeviceResolverHandlerInterceptor object to the InterceptorRegistry object in its method.

This is because the DeviceResolverHandlerInterceptor object creates a Device object and adds it as an attribute in HttpServletRequest # setAttribute before processing in the handler method.

spring-mobile/DeviceResolverHandlerInterceptor.java at v2.0.0.M3 · spring-projects/spring-mobile · GitHub

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  Device device = deviceResolver.resolveDevice(request);
  request.setAttribute(DeviceUtils.CURRENT_DEVICE_ATTRIBUTE, device);
  return true;
}

spring-mobile/DeviceUtils.java at v2.0.0.M3 · spring-projects/spring-mobile · GitHub

DeviceUtils.getCurrentDevice only returns the set attributes.

public static Device getCurrentDevice(HttpServletRequest request) {
  return (Device) request.getAttribute(CURRENT_DEVICE_ATTRIBUTE);
}

Reference material

Recommended Posts

Determine the device type of smartphones, tablets, and PCs with Spring Mobile
Feel the basic type and reference type easily with ruby
[For beginners] DI ~ The basics of DI and DI in Spring ~
Compatibility of Spring JDBC and MyBatis with Spring Data JDBC (provisional)
Access the built-in h2db of spring boot with jdbcTemplate
Until the use of Spring Data and JPA Part 2
Until the use of Spring Data and JPA Part 1
Feel the basic type and reference type easily with ruby 2
Control the processing flow of Spring Batch with JavaConfig.
Find the address class and address type from the IP address with Java
A story packed with the basics of Spring Boot (solved)
Spring validation was important in the order of Form and BindingResult
See the behavior of entity update with Spring Boot + Spring Data JPA
Coexistence of Flyway in the embedded database (h2) of the development environment and the release database (SQL Server) with Spring Boot