1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package libcore.icu;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 
21 import dalvik.annotation.compat.VersionCodes;
22 
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.text.DateFormat;
26 import java.util.Formatter;
27 
28 /**
29  * This class is only kept for @UnsupportedAppUsage, and should not be used by libcore / frameworks.
30  *
31  * @hide
32  */
33 public final class DateIntervalFormat {
34 
35     private static final int FORMAT_SHOW_TIME = 0x00001;
36     private static final int FORMAT_12HOUR = 0x00040;
37     private static final int FORMAT_24HOUR = 0x00080;
38 
DateIntervalFormat()39     private DateIntervalFormat() {
40     }
41 
42     /**
43      * Do not use this method because it's only kept for app backward compatibility.
44      * To format a date range, please use {@link android.icu.text.DateIntervalFormat} instead.
45      *
46      * @implNote best-effort implementation to invoke the frameworks' implementation. If it fails
47      * when framework.jar is absent in the boot class path, it returns null instead of
48      * throwing exception to avoid crash.
49      *
50      * @return null if the corresponding frameworks' method is not found.
51      */
52     @UnsupportedAppUsage(maxTargetSdk = VersionCodes.TIRAMISU,
53             publicAlternatives = "Use {@code android.text.format.DateUtils#formatDateRange("
54                     + "Context, Formatter, long, long, int, String)} instead.")
formatDateRange(long startMs, long endMs, int flags, String olsonId)55     public static String formatDateRange(long startMs, long endMs, int flags, String olsonId) {
56         // First, try the internal method in android.text.format.DateIntervalFormat, which
57         // can be modified by the OEMs.
58         Method m = getFrameworksDateIntervaFormatMethod();
59         if (m != null) {
60             try {
61                 return (String) m.invoke(null, startMs, endMs, flags, olsonId);
62             } catch (IllegalAccessException | InvocationTargetException e) {
63                 return null;
64             }
65         }
66 
67         // Second, try the public SDK method in android.text.format.DateUtils.
68         m = getDateUtilsMethod();
69         if (m != null) {
70             try {
71                 // To pass null frameworks' Context pointer, pre-process the flags here.
72                 if ((flags & (FORMAT_SHOW_TIME | FORMAT_12HOUR | FORMAT_24HOUR))
73                         == FORMAT_SHOW_TIME) {
74                     // Make an arbitrary choice of using 24-hour format if is24Hour is null.
75                     if (DateFormat.is24Hour == null || DateFormat.is24Hour) {
76                         flags |= FORMAT_24HOUR;
77                     } else {
78                         flags |= FORMAT_12HOUR;
79                     }
80                 }
81 
82                 Formatter formatter = new Formatter();
83                 m.invoke(null, /*context="*/ null, formatter, startMs, endMs, flags, olsonId);
84                 return formatter.toString();
85             } catch (IllegalAccessException | InvocationTargetException e) {
86                 return null;
87             }
88         }
89 
90         // If the above 2 attempts fail, it's likely that frameworks.jar is not present in the
91         // boot class path, and no 3rd party app code is running in this process. It should be safe
92         // to return null here.
93         return null;
94     }
95 
getFrameworksDateIntervaFormatMethod()96     private static Method getFrameworksDateIntervaFormatMethod() {
97         try {
98             Class<?> cls = Class.forName("android.text.format.DateIntervalFormat");
99             Method m = cls.getDeclaredMethod("formatDateRange", long.class, long.class, int.class,
100                     String.class);
101             m.setAccessible(true);
102             return m;
103         } catch (ClassNotFoundException | NoSuchMethodException e) {
104             return null;
105         }
106     }
107 
getDateUtilsMethod()108     private static Method getDateUtilsMethod() {
109         try {
110             Class<?> cls = Class.forName("android.text.format.DateUtils");
111             Method m = cls.getMethod("formatDateRange", Class.forName("android.content.Context"),
112                     Formatter.class, long.class, long.class, int.class, String.class);
113             return m;
114         } catch (ClassNotFoundException | NoSuchMethodException e) {
115             return null;
116         }
117     }
118 
119 }
120