├── .gitignore ├── .travis.yml ├── AndroidDeviceCompatibility ├── .coveralls.yml ├── .gitignore ├── build.gradle ├── coverage.gradle ├── gradle.properties ├── proguard-rules.txt └── src │ ├── androidTest │ ├── java │ │ └── jp │ │ │ └── mixi │ │ │ └── compatibility │ │ │ ├── android │ │ │ └── view │ │ │ │ └── ViewCompatTest.java │ │ │ ├── graphics │ │ │ └── BitmapCompatTest.java │ │ │ ├── os │ │ │ └── StrictModeCompatTest.java │ │ │ ├── text │ │ │ └── DateStringCompatTest.java │ │ │ ├── webkit │ │ │ └── WebViewCompatTest.java │ │ │ └── widget │ │ │ └── ArrayAdapterCompatTest.java │ └── res │ │ └── drawable │ │ ├── sample.png │ │ └── sample_big.png │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── jp │ │ └── mixi │ │ └── compatibility │ │ ├── android │ │ ├── content │ │ │ ├── ContentResolverCompat.java │ │ │ └── SharedPreferencesCompat.java │ │ ├── graphics │ │ │ └── BitmapCompat.java │ │ ├── hardware │ │ │ ├── CameraCompat.java │ │ │ └── exception │ │ │ │ └── CameraLockedException.java │ │ ├── media │ │ │ └── ExifInterfaceCompat.java │ │ ├── os │ │ │ └── StrictModeCompat.java │ │ ├── provider │ │ │ └── MediaStoreCompat.java │ │ ├── text │ │ │ └── DateStringCompat.java │ │ ├── view │ │ │ └── ViewCompat.java │ │ ├── webkit │ │ │ └── WebViewCompat.java │ │ └── widget │ │ │ ├── ArrayAdapterCompat.java │ │ │ ├── TextViewCompat.java │ │ │ └── ViewPagerContainableScrollView.java │ │ └── com │ │ └── felicanetworks │ │ └── mfc │ │ └── FelicaCompat.java │ └── res │ └── values │ └── strings.xml ├── README.md ├── build.gradle ├── circle.yml ├── conf └── checkstyle-config.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── repository └── jp │ └── mixi │ └── compatibility │ └── AndroidDeviceCompatibility │ ├── 0.1.0 │ ├── AndroidDeviceCompatibility-0.1.0.aar │ ├── AndroidDeviceCompatibility-0.1.0.aar.md5 │ ├── AndroidDeviceCompatibility-0.1.0.aar.sha1 │ ├── AndroidDeviceCompatibility-0.1.0.pom │ ├── AndroidDeviceCompatibility-0.1.0.pom.md5 │ └── AndroidDeviceCompatibility-0.1.0.pom.sha1 │ ├── 0.2.0 │ ├── AndroidDeviceCompatibility-0.2.0.aar │ ├── AndroidDeviceCompatibility-0.2.0.aar.md5 │ ├── AndroidDeviceCompatibility-0.2.0.aar.sha1 │ ├── AndroidDeviceCompatibility-0.2.0.pom │ ├── AndroidDeviceCompatibility-0.2.0.pom.md5 │ └── AndroidDeviceCompatibility-0.2.0.pom.sha1 │ ├── maven-metadata.xml │ ├── maven-metadata.xml.md5 │ └── maven-metadata.xml.sha1 ├── settings.gradle ├── wait_for_emulator └── wait_for_simulator.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Dalvik files 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | lint.xml 15 | proguard/ 16 | 17 | # Local configs 18 | local.properties 19 | 20 | # roboelectric 21 | tmp/ 22 | 23 | # Eclipse workspace 24 | .metadata/ 25 | .checkstyle 26 | .settings 27 | 28 | # intelli J 29 | .idea 30 | *.iml 31 | *.ipr 32 | *.iws 33 | 34 | # mac 35 | .Trashes 36 | .DS_Store 37 | 38 | # Win 39 | Thumbs.db 40 | 41 | # Vim 42 | *.swp 43 | 44 | # Keystore 45 | *.keystore 46 | 47 | # Gradle 48 | .gradle 49 | build/ 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | env: 3 | global: 4 | - ADB_INSTALL_TIMEOUT=10 5 | matrix: 6 | - ANDROID_TARGET=android-8 ANDROID_ABI=armeabi 7 | - ANDROID_TARGET=android-10 ANDROID_ABI=armeabi 8 | - ANDROID_TARGET=android-19 ANDROID_ABI=armeabi-v7a 9 | 10 | android: 11 | components: 12 | - tools 13 | 14 | # The BuildTools version 15 | - build-tools-19.1.0 16 | 17 | # The SDK version 18 | - $ANDROID_TARGET 19 | 20 | # Additional components 21 | - extra-android-m2repository 22 | 23 | before_script: 24 | - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI 25 | - emulator -avd test -no-skin -no-audio -no-window & 26 | - android-wait-for-emulator 27 | - adb shell input keyevent 82 & 28 | 29 | after_success: 30 | - ./gradlew -p AndroidDeviceCompatibility -b AndroidDeviceCompatibility/coverage.gradle coveralls 31 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: 2jTXILeEplfoMatN8gTHLY7AvtAfby4zC 3 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:1.1.3' 7 | } 8 | } 9 | 10 | apply plugin: 'com.android.library' 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | android { 17 | compileSdkVersion 21 18 | buildToolsVersion "21.1.2" 19 | 20 | defaultConfig { 21 | minSdkVersion 7 22 | targetSdkVersion 19 23 | } 24 | jacoco { 25 | version = '0.7.2.201409121644' 26 | } 27 | 28 | buildTypes { 29 | debug { 30 | testCoverageEnabled = true 31 | } 32 | } 33 | } 34 | 35 | tasks.withType(JavaCompile) { 36 | options.encoding = 'UTF-8' 37 | } 38 | 39 | dependencies { 40 | compile 'com.android.support:support-v4:22.2.1' 41 | compile 'com.android.support:gridlayout-v7:22.2.1' 42 | compile 'com.android.support:appcompat-v7:22.2.1' 43 | } 44 | 45 | apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle' 46 | 47 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/coverage.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:0.4.0' 8 | } 9 | } 10 | 11 | apply plugin: 'java' 12 | apply plugin: 'jacoco' 13 | apply plugin: 'coveralls' 14 | 15 | sourceSets { 16 | main { 17 | java.srcDirs = ['src/main/java'] 18 | } 19 | } 20 | 21 | coveralls.jacocoReportPath = 'build/outputs/reports/coverage/debug/report.xml' 22 | 23 | 24 | def defaultEncoding = 'UTF-8' 25 | tasks.withType(AbstractCompile) each { it.options.encoding = defaultEncoding } 26 | 27 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=AndroidDeviceCompatibility 2 | POM_ARTIFACT_ID=AndroidDeviceCompatibility 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Library/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/androidTest/java/jp/mixi/compatibility/android/view/ViewCompatTest.java: -------------------------------------------------------------------------------- 1 | package jp.mixi.compatibility.android.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Paint; 5 | import android.os.Build; 6 | import android.test.AndroidTestCase; 7 | import android.view.View; 8 | 9 | /** 10 | * Created by Hideyuki.Kikuma on 2014/10/03. 11 | * 12 | * @author Hideyuki.Kikuma 13 | */ 14 | public class ViewCompatTest extends AndroidTestCase { 15 | public void testSetLayerType() { 16 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) return; 17 | MockView view = new MockView(getContext()); 18 | Paint paint = new Paint(); 19 | ViewCompat.setLayerType(view, View.LAYER_TYPE_HARDWARE, paint); 20 | assertEquals(View.LAYER_TYPE_HARDWARE, view.mLayerType); 21 | assertEquals(paint, view.mPaint); 22 | assertTrue(view.mCalled); 23 | 24 | } 25 | 26 | public void testCallSetLayerType() { 27 | MockView view = new MockView(getContext()); 28 | ViewCompat.setLayerType(view, View.LAYER_TYPE_HARDWARE, null); 29 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 30 | assertFalse(view.mCalled); 31 | } else { 32 | assertTrue(view.mCalled); 33 | } 34 | } 35 | 36 | private class MockView extends View { 37 | int mLayerType; 38 | Paint mPaint; 39 | boolean mCalled = false; 40 | 41 | public MockView(Context context) { 42 | super(context); 43 | } 44 | 45 | @Override 46 | public void setLayerType(int layerType, Paint paint) { 47 | mCalled = true; 48 | mLayerType = layerType; 49 | mPaint = paint; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/androidTest/java/jp/mixi/compatibility/graphics/BitmapCompatTest.java: -------------------------------------------------------------------------------- 1 | package jp.mixi.compatibility.graphics; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.test.AndroidTestCase; 7 | 8 | import jp.mixi.compatibility.android.graphics.BitmapCompat; 9 | import jp.mixi.compatibility.test.R; 10 | 11 | /** 12 | * Created by kikuma on 2014/10/02. 13 | */ 14 | public class BitmapCompatTest extends AndroidTestCase { 15 | Resources mResources; 16 | 17 | @Override 18 | protected void setUp() throws Exception { 19 | super.setUp(); 20 | mResources = getContext().getResources(); 21 | } 22 | 23 | public void testGetByteCount() { 24 | Bitmap bitmap = BitmapFactory.decodeResource(mResources, R.drawable.sample); 25 | assertEquals(bitmap.getRowBytes() * bitmap.getHeight(), BitmapCompat.getByteCount(bitmap)); 26 | } 27 | 28 | public void testGetByteCountBig() { 29 | Bitmap bitmap = BitmapFactory.decodeResource(mResources, R.drawable.sample_big); 30 | assertEquals(bitmap.getRowBytes() * bitmap.getHeight(), BitmapCompat.getByteCount(bitmap)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/androidTest/java/jp/mixi/compatibility/os/StrictModeCompatTest.java: -------------------------------------------------------------------------------- 1 | package jp.mixi.compatibility.os; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.os.StrictMode; 6 | import android.test.AndroidTestCase; 7 | 8 | import jp.mixi.compatibility.android.os.StrictModeCompat; 9 | 10 | /** 11 | * Created by Hideyuki.Kikuma on 2014/10/04. 12 | */ 13 | public class StrictModeCompatTest extends AndroidTestCase { 14 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 15 | public void testEnableDefaults() { 16 | StrictModeCompat.enableDefaults(); 17 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) return; 18 | assertEquals( 19 | new StrictMode.ThreadPolicy.Builder() 20 | .detectAll() 21 | .penaltyLog() 22 | .build().toString(), 23 | StrictMode.getThreadPolicy().toString()); 24 | 25 | assertEquals(new StrictMode.VmPolicy.Builder() 26 | .detectAll() 27 | .penaltyLog() 28 | .build().toString(), 29 | StrictMode.getVmPolicy().toString()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/androidTest/java/jp/mixi/compatibility/text/DateStringCompatTest.java: -------------------------------------------------------------------------------- 1 | package jp.mixi.compatibility.text; 2 | 3 | import android.test.AndroidTestCase; 4 | import android.util.Pair; 5 | 6 | import java.util.Date; 7 | 8 | import jp.mixi.compatibility.android.text.DateStringCompat; 9 | 10 | /** 11 | * Created by Hideyuki.Kikuma on 2014/10/08. 12 | */ 13 | public class DateStringCompatTest extends AndroidTestCase { 14 | public void testFormat() { 15 | Date date = new Date("2013/1/9 08:04:05"); 16 | Pair[] array = new Pair[]{ 17 | // new Pair(format, expected), 18 | new Pair("yyyy-MM-dd HH:mm:ss", "2013-01-09 08:04:05"), 19 | new Pair("yyyy/MM/dd HH:mm:ss", "2013/01/09 08:04:05"), 20 | new Pair("MM月dd日", "01月09日"), 21 | }; 22 | for (Pair p : array) { 23 | assertEquals(p.second, DateStringCompat.format(p.first, date)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/androidTest/java/jp/mixi/compatibility/webkit/WebViewCompatTest.java: -------------------------------------------------------------------------------- 1 | package jp.mixi.compatibility.webkit; 2 | 3 | import android.content.Context; 4 | import android.test.AndroidTestCase; 5 | import android.webkit.WebView; 6 | 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.URLDecoder; 9 | 10 | import jp.mixi.compatibility.android.webkit.WebViewCompat; 11 | 12 | /** 13 | * Created by Hideyuki.Kikuma on 2014/10/08. 14 | */ 15 | public class WebViewCompatTest extends AndroidTestCase { 16 | private static final String DATA = "日本語"; 17 | private static final String MIME_TYPE = "text/plain"; 18 | 19 | public void testLoadData() throws Exception { 20 | String[] encodings = new String[]{ 21 | "shift_JIS", 22 | "shiftJIS", 23 | "utf-8", 24 | "utf8", 25 | "big5", 26 | "iso-10646-ucs-2", 27 | "utf-16", 28 | }; 29 | for (String encoding : encodings) { 30 | DummyWebView webView = new DummyWebView(getContext()); 31 | WebViewCompat.loadData(webView, DATA, MIME_TYPE, encoding); 32 | assertEquals(encoding, DATA, webView.mDecodeData); 33 | assertEquals(MIME_TYPE, webView.mMimeType); 34 | assertEquals(encoding, webView.mEncoding); 35 | } 36 | } 37 | 38 | private class DummyWebView extends WebView { 39 | private String mDecodeData; 40 | private String mMimeType; 41 | private String mEncoding; 42 | 43 | public DummyWebView(Context context) { 44 | super(context); 45 | } 46 | 47 | @Override 48 | public void loadData(String data, String mimeType, String encoding) { 49 | try { 50 | mDecodeData = URLDecoder.decode(data, encoding); 51 | } catch (UnsupportedEncodingException e) { 52 | fail(); 53 | } 54 | mMimeType = mimeType; 55 | mEncoding = encoding; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/androidTest/java/jp/mixi/compatibility/widget/ArrayAdapterCompatTest.java: -------------------------------------------------------------------------------- 1 | package jp.mixi.compatibility.widget; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.test.AndroidTestCase; 6 | import android.widget.ArrayAdapter; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.Collection; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | import jp.mixi.compatibility.android.widget.ArrayAdapterCompat; 15 | 16 | 17 | /** 18 | * Created by Hideyuki.Kikuma on 2014/10/08. 19 | */ 20 | public class ArrayAdapterCompatTest extends AndroidTestCase { 21 | 22 | public void testAddAllCollection() { 23 | List list = new ArrayList(); 24 | list.add("foo"); 25 | list.add("bar"); 26 | DummyArrayAdapter adapter = new DummyArrayAdapter(getContext(), 0); 27 | assertFalse(adapter.calledAdd); 28 | assertFalse(adapter.calledAddAll); 29 | 30 | ArrayAdapterCompat.addAll(adapter, list); 31 | 32 | assertTrue(adapter.mCollection.containsAll(list)); 33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 34 | assertTrue(adapter.calledAddAll); 35 | assertFalse(adapter.calledAdd); 36 | } else { 37 | assertTrue(adapter.calledAdd); 38 | assertFalse(adapter.calledAddAll); 39 | } 40 | } 41 | 42 | public void testAddAllArray() { 43 | String[] list = new String[]{"foo", "bar"}; 44 | DummyArrayAdapter adapter = new DummyArrayAdapter(getContext(), 0); 45 | assertFalse(adapter.calledAdd); 46 | assertFalse(adapter.calledAddAll); 47 | 48 | ArrayAdapterCompat.addAll(adapter, list); 49 | 50 | assertTrue(adapter.mCollection.containsAll(Arrays.asList(list))); 51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 52 | assertTrue(adapter.calledAddAll); 53 | assertFalse(adapter.calledAdd); 54 | } else { 55 | assertTrue(adapter.calledAdd); 56 | assertFalse(adapter.calledAddAll); 57 | } 58 | 59 | } 60 | 61 | private class DummyArrayAdapter extends ArrayAdapter { 62 | private Collection mCollection; 63 | private boolean calledAddAll = false; 64 | private boolean calledAdd = false; 65 | 66 | public DummyArrayAdapter(Context context, int resource) { 67 | super(context, resource); 68 | } 69 | 70 | @Override 71 | public void addAll(Collection collection) { 72 | calledAddAll = true; 73 | if (mCollection == null) { 74 | mCollection = new ArrayList(); 75 | } 76 | mCollection.addAll(collection); 77 | } 78 | 79 | @Override 80 | public void addAll(T... items) { 81 | calledAddAll = true; 82 | if (mCollection == null) { 83 | mCollection = new ArrayList(); 84 | } 85 | Collections.addAll(mCollection, items); 86 | } 87 | 88 | @Override 89 | public void add(T object) { 90 | calledAdd = true; 91 | if (mCollection == null) { 92 | mCollection = new ArrayList(); 93 | } 94 | mCollection.add(object); 95 | 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/androidTest/res/drawable/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixi-inc/Android-Device-Compatibility/8456458d7d24cee7c448d3eede934431645b36f9/AndroidDeviceCompatibility/src/androidTest/res/drawable/sample.png -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/androidTest/res/drawable/sample_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixi-inc/Android-Device-Compatibility/8456458d7d24cee7c448d3eede934431645b36f9/AndroidDeviceCompatibility/src/androidTest/res/drawable/sample_big.png -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/content/ContentResolverCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.content; 17 | 18 | import android.accounts.Account; 19 | import android.annotation.SuppressLint; 20 | import android.content.ContentResolver; 21 | import android.content.Context; 22 | import android.os.Build; 23 | import android.os.Bundle; 24 | 25 | /** 26 | * OS version compatibility of periodical synchronization procedure call. 27 | * @author keishin.yokomaku 28 | */ 29 | public final class ContentResolverCompat { 30 | /** 31 | * Do not instantiate this class. 32 | */ 33 | private ContentResolverCompat() {} 34 | 35 | /** 36 | * Add job queue to dispatch a sync that should be requested with the specified account, authority, and extras at the given frequency. 37 | * @param context a context. 38 | * @param account an account to add periodic sync. 39 | * @param authority an authority to dispatch periodic sync. 40 | * @param extras an bundle contains arguments for a sync adapter. 41 | * @param pollFrequency how frequently do a sync performed. 42 | */ 43 | @SuppressLint("NewApi") 44 | public static final void addPeriodicSync(Context context, Account account, String authority, Bundle extras, long pollFrequency) { 45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { 46 | // Available for periodical sync of content provider 47 | ContentResolver.addPeriodicSync(account, authority, extras, pollFrequency); 48 | } else { 49 | 50 | } 51 | } 52 | 53 | /** 54 | * Remove a periodic sync request for the account and authority. 55 | * @param context a context. 56 | * @param account an account to remove periodic sync. 57 | * @param authority an authority to cancel periodic sync. 58 | * @param extras an bundle contains arguments for a sync adapter. 59 | */ 60 | @SuppressLint("NewApi") 61 | public static final void removePeriodicSync(Context context, Account account, String authority, Bundle extras) { 62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { 63 | ContentResolver.removePeriodicSync(account, authority, extras); 64 | } else { 65 | 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/content/SharedPreferencesCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.content; 17 | 18 | import android.content.Context; 19 | import android.content.ContextWrapper; 20 | import android.content.SharedPreferences; 21 | import android.preference.PreferenceManager; 22 | import android.util.Log; 23 | 24 | import java.io.File; 25 | import java.lang.reflect.Field; 26 | import java.lang.reflect.Method; 27 | 28 | /** 29 | * Workaround helper for the issue of {@link SharedPreferences} with Galaxy S. 30 | */ 31 | public final class SharedPreferencesCompat { 32 | public static final String TAG = SharedPreferencesCompat.class.getSimpleName(); 33 | private static final String SAMSUNG_PREF_DIR_BASE = "/dbdata/databases"; 34 | private static Boolean sIsNeedPreferenceWorkaround = null; 35 | 36 | /** 37 | * Do not instantiate this class. 38 | */ 39 | private SharedPreferencesCompat() {} 40 | 41 | /** 42 | * As a workaround for Galaxy S /dbdata/databases permission bug, this 43 | * function will make an injection to the implementation of Context to 44 | * change a directory used by SharedPreferences. Called from 45 | * {@link YourApplication#onCreate()} when the application starts. 46 | * 47 | * @param context injection target context. 48 | */ 49 | public static void injectPrefencesDirIfNeeded(Context context) { 50 | if (needPreferenceWorkaround(context)) 51 | injectPreferencesDir(context); 52 | } 53 | 54 | /** 55 | * Wrapper method of getting default shared preferences. 56 | * 57 | * @param context a context 58 | * @return the default shared preferences. 59 | */ 60 | public static SharedPreferences getDefaultSharedPreferences(Context context) { 61 | return PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); 62 | } 63 | 64 | /** 65 | * Wrapper method of getting shared preferences. 66 | * 67 | * @param context a context 68 | * @param name a preference name. 69 | * @param mode operating mode. 70 | * @return a {@link SharedPreferences} for a specified name. 71 | */ 72 | public static SharedPreferences getSharedPreferences(Context context, String name, int mode) { 73 | return context.getApplicationContext().getSharedPreferences(name, mode); 74 | } 75 | 76 | /** 77 | * Checks whether we should inject to the implementation of Context for workaround or not. 78 | * 79 | * @param context a context that should be investigated. 80 | * @return true if we've lost a permission to deal with shared preferences directory. 81 | */ 82 | public static boolean needPreferenceWorkaround(Context context) { 83 | if (sIsNeedPreferenceWorkaround == null) { 84 | File baseDir = new File(SAMSUNG_PREF_DIR_BASE, context.getPackageName()); 85 | boolean result = baseDir.exists() && !baseDir.canWrite(); 86 | if (result) 87 | Log.v(TAG, "need /dbdata workaround"); 88 | sIsNeedPreferenceWorkaround = result; 89 | } 90 | return sIsNeedPreferenceWorkaround.booleanValue(); 91 | } 92 | 93 | /** 94 | * Injects {@link SharedPreferences} directory to enable using proper {@link SharedPreferences}. 95 | * 96 | * @param context a context. 97 | */ 98 | private static void injectPreferencesDir(Context context) { 99 | if (ContextWrapper.class.isInstance(context)) { 100 | ContextWrapper wrapper = (ContextWrapper) context; 101 | context = wrapper.getBaseContext(); 102 | } 103 | 104 | // inject to overwrite inner ContextImpl#mPreferencesDir 105 | try { 106 | Class clazz = Class.forName("android.app.ContextImpl"); 107 | 108 | Field field = clazz.getDeclaredField("mPreferencesDir"); 109 | field.setAccessible(true); 110 | if (field.get(context) != null) 111 | return; 112 | 113 | // build shared_prefs path and try to overwrite 114 | Method getDataDirFile = clazz.getDeclaredMethod("getDataDirFile"); 115 | getDataDirFile.setAccessible(true); 116 | File dataDir = (File) getDataDirFile.invoke(context); 117 | 118 | field.set(context, new File(dataDir, "shared_prefs")); 119 | } catch (Exception ignored) { 120 | Log.w(TAG, "failed to apply Samsung Galaxy broken /dbdata workaround.", ignored); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/graphics/BitmapCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.graphics; 17 | 18 | import android.annotation.SuppressLint; 19 | import android.graphics.Bitmap; 20 | import android.os.Build; 21 | 22 | /** 23 | * OS version compatibility of bitmap API. 24 | * @author keishin.yokomaku 25 | */ 26 | public final class BitmapCompat { 27 | private BitmapCompat() {} 28 | 29 | /** 30 | * Returns the number of bytes used to store the pixels. 31 | * @param bitmap to be calculated byte count. 32 | * @return bytes used to store the pixels. 33 | */ 34 | @SuppressLint("NewApi") 35 | public static final int getByteCount(Bitmap bitmap) { 36 | // Available from API Level 12 37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { 38 | return bitmap.getByteCount(); 39 | } else { 40 | // Back-port for the earlier releases 41 | return bitmap.getRowBytes() * bitmap.getHeight(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/hardware/CameraCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.hardware; 17 | 18 | import android.annotation.SuppressLint; 19 | import android.annotation.TargetApi; 20 | import android.app.admin.DevicePolicyManager; 21 | import android.content.Context; 22 | import android.hardware.Camera; 23 | import android.os.Build; 24 | import android.util.Log; 25 | 26 | import jp.mixi.compatibility.android.hardware.exception.CameraLockedException; 27 | 28 | /** 29 | * Utility class for the use of hardware {@link android.hardware.Camera}. 30 | * @author keishin.yokomaku 31 | */ 32 | @SuppressWarnings("unused") 33 | public final class CameraCompat { 34 | public static final String TAG = CameraCompat.class.getSimpleName(); 35 | private CameraCompat() {} 36 | 37 | /** 38 | * Tries to open the camera. 39 | * If the camera is in use by another application, or disabled by the device admin, 40 | * this method will return null. 41 | * @param cameraId to open 42 | * @return {@link android.hardware.Camera} instance if available. null if not available for this application process. 43 | */ 44 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 45 | @SuppressLint("NewApi") 46 | public static Camera tryOpen(Context context, int cameraId) { 47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 48 | DevicePolicyManager manager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 49 | if (manager.getCameraDisabled(null)) { 50 | return null; 51 | } 52 | } 53 | try { 54 | return tryOpenOrThrow(cameraId); 55 | } catch (CameraLockedException e) { // if someone has a lock for hardware camera. 56 | Log.e(TAG, "It seems someone keeps using a camera, so we cannot open the camera.", e); 57 | return null; 58 | } 59 | } 60 | 61 | /** 62 | * Tries to open the camera. 63 | * If the camera is in use by another application, this method will throw a {@link jp.mixi.compatibility.android.hardware.exception.CameraLockedException}. 64 | * @param cameraId to open 65 | * @return {@link android.hardware.Camera} instance if available. 66 | * @throws CameraLockedException if the camera is not available for this application process. 67 | */ 68 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 69 | public static Camera tryOpenOrThrow(int cameraId) throws CameraLockedException { 70 | try { 71 | return Camera.open(cameraId); 72 | } catch (RuntimeException e) { 73 | throw new CameraLockedException("It seems someone keeps using a camera, or disabled by the admin of this device, so we cannot open the camera.", e); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/hardware/exception/CameraLockedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.hardware.exception; 17 | 18 | /** 19 | * Signals an error on {@link android.hardware.Camera} usage. 20 | * Especially the case if the camera is locked by another application, or disabled by the device admin. 21 | * @author keishin.yokomaku 22 | */ 23 | @SuppressWarnings("unused") // public API 24 | public class CameraLockedException extends Exception { 25 | private static final long serialVersionUID = 6897148507275774935L; 26 | 27 | public CameraLockedException() { 28 | super(); 29 | } 30 | 31 | public CameraLockedException(String detailMessage) { 32 | super(detailMessage); 33 | } 34 | 35 | public CameraLockedException(String detailMessage, Throwable throwable) { 36 | super(detailMessage, throwable); 37 | } 38 | 39 | public CameraLockedException(Throwable throwable) { 40 | super(throwable); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/media/ExifInterfaceCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.media; 17 | 18 | import android.media.ExifInterface; 19 | import android.text.TextUtils; 20 | import android.util.Log; 21 | 22 | import java.io.IOException; 23 | import java.text.ParseException; 24 | import java.text.SimpleDateFormat; 25 | import java.util.Date; 26 | import java.util.TimeZone; 27 | 28 | import static jp.mixi.compatibility.android.media.ExifInterfaceCompat.EXIF_DEGREE_FALLBACK_VALUE; 29 | import static jp.mixi.compatibility.android.media.ExifInterfaceCompat.getExifDateTime; 30 | 31 | /** 32 | * Bug fixture for ExifInterface constructor. 33 | */ 34 | public final class ExifInterfaceCompat { 35 | public static final String TAG = ExifInterfaceCompat.class.getSimpleName(); 36 | public static final int EXIF_DEGREE_FALLBACK_VALUE = -1; 37 | 38 | /** 39 | * Do not instantiate this class. 40 | */ 41 | private ExifInterfaceCompat() {} 42 | 43 | /** 44 | * Creates new instance of {@link ExifInterface}. 45 | * Original constructor won't check filename value, so if null value has been passed, 46 | * the process will be killed because of SIGSEGV. 47 | * Google Play crash report system cannot perceive this crash, so this method will throw {@link NullPointerException} when the filename is null. 48 | * 49 | * @param filename a JPEG filename. 50 | * @return {@link ExifInterface} instance. 51 | * @throws IOException something wrong with I/O. 52 | */ 53 | public static final ExifInterface newInstance(String filename) throws IOException { 54 | if (filename == null) throw new NullPointerException("filename should not be null"); 55 | return new ExifInterface(filename); 56 | } 57 | 58 | public static final Date getExifDateTime(String filepath) { 59 | ExifInterface exif = null; 60 | try { 61 | // ExifInterface does not check whether file path is null or not, 62 | // so passing null file path argument to its constructor causing SIGSEGV. 63 | // We should avoid such a situation by checking file path string. 64 | exif = ExifInterfaceCompat.newInstance(filepath); 65 | } catch (IOException ex) { 66 | Log.e(TAG, "cannot read exif", ex); 67 | return null; 68 | } 69 | 70 | String date = exif.getAttribute(ExifInterface.TAG_DATETIME); 71 | if (TextUtils.isEmpty(date)) { 72 | return null; 73 | } 74 | try { 75 | SimpleDateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); 76 | formatter.setTimeZone(TimeZone.getTimeZone("UTC")); 77 | return formatter.parse(date); 78 | } catch (ParseException e) { 79 | Log.d(TAG, "failed to parse date taken", e); 80 | } 81 | return null; 82 | } 83 | 84 | /** 85 | * Read exif info and get datetime value of the photo. 86 | * @param filepath to get datetime 87 | * @return when a photo taken. 88 | */ 89 | public static final long getExifDateTimeInMillis(String filepath) { 90 | Date datetime = getExifDateTime(filepath); 91 | if (datetime == null) { 92 | return -1; 93 | } 94 | return datetime.getTime(); 95 | } 96 | 97 | /** 98 | * Read exif info and get orientation value of the photo. 99 | * @param filepath to get exif. 100 | * @return exif orientation value 101 | */ 102 | public static final int getExifOrientation(String filepath) { 103 | ExifInterface exif = null; 104 | try { 105 | // ExifInterface does not check whether file path is null or not, 106 | // so passing null file path argument to its constructor causing SIGSEGV. 107 | // We should avoid such a situation by checking file path string. 108 | exif = ExifInterfaceCompat.newInstance(filepath); 109 | } catch (IOException ex) { 110 | Log.e(TAG, "cannot read exif", ex); 111 | return EXIF_DEGREE_FALLBACK_VALUE; 112 | } 113 | 114 | int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, EXIF_DEGREE_FALLBACK_VALUE); 115 | if (orientation == EXIF_DEGREE_FALLBACK_VALUE) { 116 | return 0; 117 | } 118 | // We only recognize a subset of orientation tag values. 119 | switch (orientation) { 120 | case ExifInterface.ORIENTATION_ROTATE_90: 121 | return 90; 122 | case ExifInterface.ORIENTATION_ROTATE_180: 123 | return 180; 124 | case ExifInterface.ORIENTATION_ROTATE_270: 125 | return 270; 126 | default: 127 | return 0; 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/os/StrictModeCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.os; 17 | 18 | import android.annotation.TargetApi; 19 | import android.os.Build; 20 | import android.os.StrictMode; 21 | import android.util.Log; 22 | 23 | /** 24 | * OS version compatibility to enable strict mode later than {@link Build.VERSION_CODES#FROYO}. 25 | * 26 | * @author keishin.yokomaku 27 | */ 28 | public final class StrictModeCompat { 29 | public static final String TAG = StrictModeCompat.class.getSimpleName(); 30 | 31 | private StrictModeCompat() { 32 | } 33 | 34 | /** 35 | * Enable default strict mode. 36 | * Earlier than Gingerbread, this method will do nothing. 37 | */ 38 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 39 | public static final void enableDefaults() { 40 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) { 41 | Log.v(TAG, "enabling StrictMode in default parameters"); 42 | StrictMode.enableDefaults(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/provider/MediaStoreCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.provider; 17 | 18 | import android.annotation.TargetApi; 19 | import android.app.Activity; 20 | import android.content.ContentResolver; 21 | import android.content.ContentUris; 22 | import android.content.ContentValues; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.pm.PackageManager; 26 | import android.database.ContentObserver; 27 | import android.database.Cursor; 28 | import android.media.ExifInterface; 29 | import android.net.Uri; 30 | import android.os.Build; 31 | import android.os.Environment; 32 | import android.os.Handler; 33 | import android.provider.MediaStore; 34 | import android.text.TextUtils; 35 | import android.util.Log; 36 | 37 | import jp.mixi.compatibility.android.media.ExifInterfaceCompat; 38 | 39 | import java.io.File; 40 | import java.io.FileInputStream; 41 | import java.io.FileOutputStream; 42 | import java.io.IOException; 43 | import java.nio.channels.FileChannel; 44 | import java.text.ParseException; 45 | import java.text.SimpleDateFormat; 46 | import java.util.ArrayList; 47 | import java.util.Date; 48 | import java.util.TimeZone; 49 | 50 | /** 51 | * Compatibility class for the issue of various implementations of camera feature. 52 | */ 53 | public class MediaStoreCompat { 54 | public static final String TAG = MediaStoreCompat.class.getSimpleName(); 55 | private static final String MEDIA_FILE_NAME_FORMAT = "yyyyMMdd_HHmmss"; 56 | private static final String MEDIA_FILE_EXTENSION = ".jpg"; 57 | private static final String MEDIA_FILE_PREFIX = "IMG_"; 58 | private final String MEDIA_FILE_DIRECTORY; 59 | private Context mContext; 60 | private ContentObserver mObserver; 61 | private ArrayList mRecentlyUpdatedPhotos; 62 | 63 | /** 64 | * Prepares to deal with various implementations of camera feature and {@link MediaStore}. 65 | * You should call {@link MediaStoreCompat#destroy()} on destroying context. 66 | * 67 | * @param context a context 68 | * @param handler a handler 69 | */ 70 | public MediaStoreCompat(Context context, Handler handler) { 71 | mContext = context; 72 | MEDIA_FILE_DIRECTORY = context.getPackageName(); 73 | mObserver = new ContentObserver(handler) { 74 | @Override 75 | public void onChange(boolean selfChange) { 76 | super.onChange(selfChange); 77 | updateLatestPhotos(); 78 | } 79 | }; 80 | mContext.getContentResolver().registerContentObserver( 81 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, mObserver); 82 | } 83 | 84 | /** 85 | * Checks whether the device has a camera feature or not. 86 | * @param context a context to check for camera feature. 87 | * @return true if the device has a camera feature. false otherwise. 88 | */ 89 | public static final boolean hasCameraFeature(Context context) { 90 | PackageManager pm = context.getApplicationContext().getPackageManager(); 91 | return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA); 92 | } 93 | 94 | /** 95 | * Invokes a camera capture activity. 96 | * @param activity the caller of the camera capture activity. 97 | * @param requestCode activity result handling id. 98 | * @return a file name to be saved as. 99 | */ 100 | public String invokeCameraCapture(Activity activity, int requestCode) { 101 | if (!hasCameraFeature(mContext)) return null; 102 | 103 | File toSave = getOutputFileUri(); 104 | if (toSave == null) return null; 105 | 106 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 107 | intent.addCategory(Intent.CATEGORY_DEFAULT); 108 | intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(toSave)); 109 | intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); 110 | activity.startActivityForResult(intent, requestCode); 111 | return toSave.toString(); 112 | } 113 | 114 | /** 115 | * Should be called on destroying context to dereference to the {@link ContentObserver}. 116 | */ 117 | public void destroy() { 118 | mContext.getContentResolver().unregisterContentObserver(mObserver); 119 | } 120 | 121 | /** 122 | * @param data the result {@link Intent} of a camera activity. 123 | * @param preparedUri a prepared photo uri to fetch photo data. 124 | * @return captured photo {@link Uri} 125 | */ 126 | public Uri getCapturedPhotoUri(Intent data, String preparedUri) { 127 | Uri captured = null; 128 | if (data != null) { 129 | captured = data.getData(); 130 | if (captured == null) data.getParcelableExtra(Intent.EXTRA_STREAM); 131 | } 132 | 133 | File prepared = new File(preparedUri.toString()); 134 | if (captured == null || captured.equals(Uri.fromFile(prepared))) { 135 | captured = findPhotoFromRecentlyTaken(prepared); 136 | if (captured == null) { 137 | captured = storeImage(prepared); 138 | prepared.delete(); 139 | } else { 140 | // Found. Compare the destination path 141 | // and delete app-private one if there is any copy outside of app-private directory. 142 | String realPath = getPathFromUri( 143 | mContext.getContentResolver(), captured); 144 | if (realPath != null) { 145 | if (! prepared.equals(new File(realPath))) prepared.delete(); 146 | } 147 | } 148 | } 149 | 150 | return captured; 151 | } 152 | 153 | /** 154 | * Deletes unnecessary files if exist. 155 | * @param uri to delete. 156 | */ 157 | public void cleanUp(String uri) { 158 | File file = new File(uri.toString()); 159 | if (file.exists()) file.delete(); 160 | } 161 | 162 | /** 163 | * Obtains a captured photo file path from content {@link Uri} of a {@link MediaStore} 164 | * @param resolver a content resolver 165 | * @param contentUri a content uri 166 | * @return captured photo file path string 167 | */ 168 | public static String getPathFromUri(ContentResolver resolver, Uri contentUri) { 169 | final String dataColumn = MediaStore.MediaColumns.DATA; 170 | Cursor cursor = null; 171 | try { 172 | cursor = resolver.query(contentUri, new String[] { dataColumn }, null, null, null); 173 | if (cursor == null || !cursor.moveToFirst()) return null; 174 | 175 | final int index = cursor.getColumnIndex(dataColumn); 176 | return cursor.getString(index); 177 | } finally { 178 | if (cursor != null) cursor.close(); 179 | } 180 | } 181 | 182 | /** 183 | * Copies file streams. 184 | * @param is input stream 185 | * @param os output stream 186 | * @return the number of bytes. 187 | * @throws IOException if something wrong with I/O. 188 | */ 189 | public static long copyFileStream(FileInputStream is, FileOutputStream os) 190 | throws IOException { 191 | FileChannel srcChannel = null; 192 | FileChannel destChannel = null; 193 | try { 194 | srcChannel = is.getChannel(); 195 | destChannel = os.getChannel(); 196 | return srcChannel.transferTo(0, srcChannel.size(), destChannel); 197 | } finally { 198 | if (srcChannel != null) srcChannel.close(); 199 | if (destChannel != null) destChannel.close(); 200 | } 201 | } 202 | 203 | /** 204 | * Obtains file {@link Uri} that refers to a recently taken photo. 205 | * @param file to search 206 | * @return photo file uri. 207 | */ 208 | private Uri findPhotoFromRecentlyTaken(File file) { 209 | if (mRecentlyUpdatedPhotos == null) 210 | updateLatestPhotos(); 211 | 212 | long filesize = file.length(); 213 | long taken = ExifInterfaceCompat.getExifDateTimeInMillis(file.getAbsolutePath()); 214 | 215 | int maxPoint = 0; 216 | PhotoContent maxItem = null; 217 | for (PhotoContent item : mRecentlyUpdatedPhotos) { 218 | int point = 0; 219 | if (item.size == filesize) 220 | point++; 221 | if (item.taken == taken) 222 | point++; 223 | if (point > maxPoint) { 224 | maxPoint = point; 225 | maxItem = item; 226 | } 227 | } 228 | if (maxItem != null) { 229 | generateThumbnails(maxItem.id); 230 | return ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 231 | maxItem.id); 232 | } 233 | 234 | return null; 235 | } 236 | 237 | /** 238 | * Stores a file to media store with photo orientation and date that taken. 239 | * 240 | * @param file to store 241 | * @return a stored file uri 242 | */ 243 | private Uri storeImage(File file) { 244 | // store image to content provider 245 | try { 246 | // create parameters for Intent with filename 247 | ContentValues values = new ContentValues(); 248 | values.put(MediaStore.Images.Media.TITLE, file.getName()); 249 | values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); 250 | values.put(MediaStore.Images.Media.DESCRIPTION, "mixi Photo"); 251 | values.put(MediaStore.Images.Media.ORIENTATION, 252 | ExifInterfaceCompat.getExifOrientation(file.getAbsolutePath())); 253 | long date = ExifInterfaceCompat.getExifDateTimeInMillis(file.getAbsolutePath()); 254 | if (date != -1) 255 | values.put(MediaStore.Images.Media.DATE_TAKEN, date); 256 | 257 | Uri imageUri = mContext.getContentResolver().insert( 258 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 259 | 260 | FileOutputStream fos = (FileOutputStream) mContext.getContentResolver().openOutputStream( 261 | imageUri); 262 | FileInputStream fis = new FileInputStream(file); 263 | copyFileStream(fis, fos); 264 | fos.close(); 265 | fis.close(); 266 | 267 | generateThumbnails(ContentUris.parseId(imageUri)); 268 | 269 | return imageUri; 270 | } catch (Exception e) { 271 | Log.w(TAG, "cannot insert", e); 272 | return null; 273 | } 274 | } 275 | 276 | /** 277 | * Request to update latest photos onto media store. 278 | */ 279 | private void updateLatestPhotos() { 280 | // retrieve the newest five images 281 | Cursor c = MediaStore.Images.Media.query(mContext.getContentResolver(), 282 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { 283 | MediaStore.Images.ImageColumns._ID, 284 | MediaStore.Images.ImageColumns.DATE_TAKEN, 285 | MediaStore.Images.ImageColumns.SIZE, 286 | }, null, null, MediaStore.Images.ImageColumns.DATE_ADDED + " DESC"); 287 | if (c != null) { 288 | try { 289 | int count = 0; 290 | mRecentlyUpdatedPhotos = new ArrayList(); 291 | while (c.moveToNext()) { 292 | PhotoContent item = new PhotoContent(); 293 | item.id = c.getLong(0); 294 | item.taken = c.getLong(1); 295 | item.size = c.getInt(2); 296 | mRecentlyUpdatedPhotos.add(item); 297 | if (++count > 5) 298 | break; 299 | } 300 | } finally { 301 | c.close(); 302 | } 303 | } 304 | } 305 | 306 | /** 307 | * Create thumbnail images for a specified photo image. 308 | * 309 | * @param imageId referes to a photo image. 310 | */ 311 | private void generateThumbnails(long imageId) { 312 | try { 313 | MediaStore.Images.Thumbnails.getThumbnail(mContext.getContentResolver(), imageId, 314 | MediaStore.Images.Thumbnails.MINI_KIND, null); 315 | } catch (NullPointerException e) { 316 | // some MediaStores throws NPE, just ignore the result 317 | } 318 | } 319 | 320 | /** 321 | * Make output file uri based on a external storage directory. 322 | * @return a file 323 | */ 324 | @TargetApi(Build.VERSION_CODES.FROYO) 325 | private File getOutputFileUri() { 326 | File extDir = new File( 327 | Environment.getExternalStoragePublicDirectory( 328 | Environment.DIRECTORY_PICTURES), 329 | MEDIA_FILE_DIRECTORY); 330 | 331 | if (!extDir.exists()) { 332 | if (!extDir.mkdirs()) return null; 333 | } 334 | 335 | String timeStamp = new SimpleDateFormat(MEDIA_FILE_NAME_FORMAT).format(new Date()); 336 | return new File(extDir.getPath() + File.separator + 337 | MEDIA_FILE_PREFIX + timeStamp + MEDIA_FILE_EXTENSION); 338 | } 339 | 340 | /** 341 | * Entity class for photo content. 342 | */ 343 | private static class PhotoContent { 344 | public long id; 345 | public long taken; 346 | public int size; 347 | } 348 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/text/DateStringCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.text; 17 | 18 | import java.text.SimpleDateFormat; 19 | import java.util.Date; 20 | import java.util.Locale; 21 | 22 | /** 23 | * Date formatting issues workaround depends on a device specific implementation. 24 | */ 25 | public final class DateStringCompat { 26 | /** 27 | * Do not instantiate this class. 28 | */ 29 | private DateStringCompat() {} 30 | 31 | /** 32 | * Some HTC devices return wrong formatted text from DateFormat. 33 | * ex. DateFormat.format("MM月DD日", new Date("2013/1/25")) 34 | * => expected:<01月25[日]> but was:<01月25[]> 35 | * As a workaround for HTC device's bug, 36 | * this function uses {@link SimpleDateFormat} for now. 37 | * 38 | * @param format please refer to the {@link SimpleDateFormat} 39 | * @param date to be formatted 40 | * @return formatted string 41 | */ 42 | public static String format(String format, Date date) { 43 | SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US); 44 | return sdf.format(date); 45 | } 46 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/view/ViewCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.view; 17 | 18 | import android.annotation.TargetApi; 19 | import android.graphics.Paint; 20 | import android.os.Build; 21 | import android.view.View; 22 | 23 | /** 24 | * OS version compatibility class for recently added APIs of {@link View} class. 25 | * 26 | * @author keishin.yokomaku 27 | */ 28 | public final class ViewCompat { 29 | public static final String TAG = ViewCompat.class.getSimpleName(); 30 | 31 | private ViewCompat() { 32 | } 33 | 34 | /** 35 | * Specifies the type of layer backing this view. 36 | * 37 | * @param view Target {@link View} 38 | * @param layerType One of {@link View#LAYER_TYPE_NONE}, {@link View#LAYER_TYPE_SOFTWARE} or {@link View#LAYER_TYPE_HARDWARE}. 39 | * @param paint Optional {@link Paint} instance that controls how the layer is composed on screen. 40 | * @see View#setLayerType(int, Paint) 41 | */ 42 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 43 | public static final void setLayerType(final View view, final int layerType, final Paint paint) { 44 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 45 | return; 46 | } 47 | view.setLayerType(layerType, paint); 48 | } 49 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/webkit/WebViewCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.webkit; 17 | 18 | import android.webkit.WebView; 19 | 20 | import java.io.UnsupportedEncodingException; 21 | import java.net.URLEncoder; 22 | 23 | /** 24 | * Compatibility class for the issue of various implementations of WebView. 25 | */ 26 | public final class WebViewCompat { 27 | /** 28 | * Do not instantiate this. 29 | */ 30 | private WebViewCompat() {} 31 | 32 | /** 33 | * Workaround for a bug of http://code.google.com/p/android/issues/detail?id=4401 34 | * 35 | * Some devices cannot render with a un-url-encoded html text, 36 | * as {@link WebView#loadData(String, String, String)} will load html data as a data scheme url, 37 | * so we need to url-encode html text before loading html data. 38 | * @param webview a webview to load the data. 39 | * @param data the html data. 40 | * @param mimeType a mime type of the data. 41 | * @param encoding an encoding of the data. 42 | * @throws UnsupportedEncodingException if a specified encoding is not supported. 43 | */ 44 | public static final void loadData(WebView webview, String data, String mimeType, String encoding) throws UnsupportedEncodingException { 45 | String encoded = URLEncoder.encode(data, encoding); 46 | webview.loadData(encoded, mimeType, encoding); 47 | } 48 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/widget/ArrayAdapterCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.widget; 17 | 18 | import android.annotation.SuppressLint; 19 | import android.annotation.TargetApi; 20 | import android.os.Build; 21 | import android.widget.ArrayAdapter; 22 | 23 | import java.util.Collection; 24 | 25 | /** 26 | * Compatibility class for a difference of android framework API levels. 27 | */ 28 | public final class ArrayAdapterCompat { 29 | /** 30 | * Do not instantiate this class. 31 | */ 32 | private ArrayAdapterCompat() { 33 | } 34 | 35 | /** 36 | * Add all elements in the collection to the end of the adapter. 37 | * 38 | * @param adapter to be added. 39 | * @param list to add all elements. 40 | * @param generic type for the collection. 41 | */ 42 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 43 | public static final void addAll(ArrayAdapter adapter, Collection list) { 44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 45 | adapter.addAll(list); 46 | } else { 47 | for (T element : list) { 48 | adapter.add(element); 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * Add all elements in the array to the end of the adapter. 55 | * 56 | * @param adapter to be added 57 | * @param array to add all elements 58 | * @param generic type for an array. 59 | */ 60 | @SuppressLint("NewApi") 61 | public static final void addAll(ArrayAdapter adapter, T... array) { 62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 63 | adapter.addAll(array); 64 | } else { 65 | for (T element : array) { 66 | adapter.add(element); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/widget/TextViewCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.android.widget; 17 | 18 | import android.content.Context; 19 | import android.util.AttributeSet; 20 | import android.widget.TextView; 21 | 22 | /** 23 | * Workaround for the following issue of {@link TextView}. 24 | * - Issue #6716343 25 | * -- Reported at: http://code.google.com/p/android/issues/detail?id=35259 26 | * -- Reported at: http://code.google.com/p/android/issues/detail?id=35466 27 | */ 28 | public class TextViewCompat extends TextView { 29 | /** 30 | * Initialize text view for a specified context. 31 | * @param context a context for this view 32 | * @param attrs attributes for this view 33 | * @param defStyle styles for this view 34 | */ 35 | public TextViewCompat(Context context, AttributeSet attrs, int defStyle) { 36 | super(context, attrs, defStyle); 37 | } 38 | 39 | /** 40 | * Initialize text view for a specified context. 41 | * @param context a context for this view 42 | * @param attrs attributes for this view 43 | */ 44 | public TextViewCompat(Context context, AttributeSet attrs) { 45 | super(context, attrs); 46 | } 47 | 48 | /** 49 | * Initialize text view for a specified context. 50 | * @param context a context for this view 51 | */ 52 | public TextViewCompat(Context context) { 53 | super(context); 54 | } 55 | 56 | @Override 57 | public void setText(CharSequence text, BufferType type) { 58 | try { 59 | super.setText(text, type); 60 | } catch (ArrayIndexOutOfBoundsException exp) { 61 | // workaround of android framework issue #6716343 62 | if (text != null) super.setText(text.toString()); 63 | else super.setText(null); 64 | } 65 | } 66 | 67 | @Override 68 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 69 | try { 70 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 71 | } catch (ArrayIndexOutOfBoundsException exp) { 72 | // workaround of android framework issue #6716343 73 | CharSequence c = getText(); 74 | if (c != null) { 75 | super.setText(c.toString()); 76 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/android/widget/ViewPagerContainableScrollView.java: -------------------------------------------------------------------------------- 1 | package jp.mixi.compatibility.android.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.GestureDetector; 6 | import android.view.MotionEvent; 7 | import android.widget.ScrollView; 8 | 9 | /** 10 | * This is a {@link android.widget.ScrollView} compatibility that enables containing {@link android.support.v4.view.ViewPager}. 11 | * {@link android.support.v4.view.ViewPager} swipe action will not be properly performed on some devices while it is in the scrollable component, so use this to avoid such a problem. 12 | */ 13 | @SuppressWarnings("unused") // public APIs 14 | public class ViewPagerContainableScrollView extends ScrollView { 15 | private GestureDetector mGestureDetector; 16 | 17 | public ViewPagerContainableScrollView(Context context) { 18 | this(context, null); 19 | } 20 | 21 | public ViewPagerContainableScrollView(Context context, AttributeSet attrs) { 22 | this(context, attrs, 0); 23 | } 24 | 25 | public ViewPagerContainableScrollView(Context context, AttributeSet attrs, int defStyle) { 26 | super(context, attrs, defStyle); 27 | 28 | mGestureDetector = new GestureDetector(context, new YScrollDetector()); 29 | setFadingEdgeLength(0); 30 | } 31 | 32 | @Override 33 | public boolean onInterceptTouchEvent(MotionEvent ev) { 34 | return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev); 35 | } 36 | 37 | private static class YScrollDetector extends GestureDetector.SimpleOnGestureListener { 38 | @Override 39 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 40 | if(Math.abs(distanceY) > Math.abs(distanceX)) { 41 | return true; 42 | } 43 | return false; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/java/jp/mixi/compatibility/com/felicanetworks/mfc/FelicaCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 mixi, Inc. All rights reserved. 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 | package jp.mixi.compatibility.com.felicanetworks.mfc; 17 | 18 | import android.os.Build; 19 | 20 | /** 21 | * Device-specific compatibility class for FeliCa implementation. 22 | * 23 | * @author keishin.yokomaku 24 | */ 25 | public final class FelicaCompat { 26 | public static final String[] FELICA_PUSH_UNSUPPORTED_MODELS = new String[] { 27 | "IS04", "T-01C" 28 | }; 29 | 30 | /** 31 | * Do not instantiate this class. 32 | */ 33 | private FelicaCompat() {} 34 | 35 | /** 36 | * Checks whether FeliCa push feature is available or not. 37 | * FeliCa push feature is not available on some devices, but we cannot detect it at runtime. 38 | * 39 | * @return true if FeliCa push feature is available, false otherwise. 40 | */ 41 | public static boolean isFeliCaPushAvailable() { 42 | for (String model : FELICA_PUSH_UNSUPPORTED_MODELS) { 43 | if (model.equals(Build.MODEL)) return false; 44 | } 45 | return true; 46 | } 47 | } -------------------------------------------------------------------------------- /AndroidDeviceCompatibility/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidDeviceCompatibility 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Device Compatibility Package 2 | ====== 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/jp.mixi/AndroidDeviceCompatibility/badge.svg)](https://maven-badges.herokuapp.com/maven-central/jp.mixi/AndroidDeviceCompatibility/) 4 | [![Build Status](https://travis-ci.org/mixi-inc/Android-Device-Compatibility.svg?branch=master)](https://travis-ci.org/mixi-inc/Android-Device-Compatibility) 5 | [![Build Status](https://circleci.com/gh/mixi-inc/Android-Device-Compatibility.svg?style=shield&circle-token=2f3ead4c64a786dbd7e87d557a815bb8eafd05af)](https://circleci.com/gh/mixi-inc/Android-Device-Compatibility/tree/master) 6 | [![License](http://img.shields.io/badge/license-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) 7 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Android%20Device%20Compatibility%20Package-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/1370) 8 | 9 | Yet another compatibility package of android. This project aims to make the app compatible with various devices all over the world. 10 | The project will take care about lots of issues caused by device differences, so you don't need to write a weird compatibility code on your own. 11 | 12 | ![](https://farm5.staticflickr.com/4032/4302079406_aab8748987_o_d.jpg) 13 | [Photo license](https://creativecommons.org/licenses/by/2.0/) 14 | 15 | Features 16 | ------- 17 | 18 | * Workarounds for device-specific problems 19 | * OS version compatibility 20 | * OS framework bugfixes 21 | 22 | Requirements 23 | ------- 24 | 25 | * Supports Android 2.1 or greater. 26 | 27 | Download 28 | ------- 29 | 30 | Via Gradle 31 | 32 | ```groovy 33 | compile 'jp.mixi:AndroidDeviceCompatibility:2.0.0' 34 | ``` 35 | 36 | Contributing 37 | ------- 38 | 39 | Any contributions, bug fixes, unit/integration tests are welcomed and appreciated. 40 | Please fork this repository and send pull requests to contribute. 41 | 42 | License 43 | ------- 44 | 45 | ``` 46 | Copyright (C) 2012 mixi, Inc. All rights reserved. 47 | 48 | Licensed under the Apache License, Version 2.0 (the "License"); 49 | you may not use this file except in compliance with the License. 50 | You may obtain a copy of the License at 51 | 52 | http://www.apache.org/licenses/LICENSE-2.0 53 | 54 | Unless required by applicable law or agreed to in writing, software 55 | distributed under the License is distributed on an "AS IS" BASIS, 56 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 57 | See the License for the specific language governing permissions and 58 | limitations under the License. 59 | ``` 60 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:1.1.3' 8 | } 9 | } -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | java: 3 | version: openjdk7 4 | environment: 5 | ANDROID_HOME: /usr/local/android-sdk-linux 6 | JAVA_OPTS: "-Xmx2048m -XX:MaxPermSize=1024m" 7 | ADB_INSTALL_TIMEOUT: 10 # minutes 8 | 9 | dependencies: 10 | override: 11 | - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2,extra-android-m2repository" 12 | 13 | test: 14 | override: 15 | # start the emulator 16 | - emulator -avd circleci-android22 -no-audio -no-window: 17 | background: true 18 | parallel: true 19 | # wait for it to have booted 20 | - circle-android wait-for-boot 21 | - ./gradlew connectedAndroidTest test 22 | # copy the build outputs to artifacts 23 | - cp -r AndroidDeviceCompatibility/build/outputs $CIRCLE_ARTIFACTS 24 | # copy the test results to the test results directory. 25 | - cp -r AndroidDeviceCompatibility/build/outputs/androidTest-results/* $CIRCLE_TEST_REPORTS 26 | 27 | -------------------------------------------------------------------------------- /conf/checkstyle-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Set Gradle settings which apply to all modules here. 2 | VERSION_NAME=2.0.0 3 | VERSION_CODE=2 4 | GROUP=jp.mixi 5 | 6 | POM_DESCRIPTION=Compatibility package project for android device difference. 7 | POM_URL=https://github.com/mixi-inc/Android-Device-Compatibility/ 8 | POM_SCM_URL=https://github.com/mixi-inc/Android-Device-Compatibility/ 9 | POM_SCM_CONNECTION=scm:git@github.com:mixi-inc/Android-Device-Compatibility.git 10 | POM_SCM_DEV_CONNECTION=scm:git@github.com:mixi-inc/Android-Device-Compatibility.git 11 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 12 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 13 | POM_LICENCE_DIST=repo 14 | POM_DEVELOPER_ID=mixi 15 | POM_DEVELOPER_NAME=mixi,Inc. 16 | 17 | ANDROID_BUILD_TARGET_SDK_VERSION=19 18 | ANDROID_BUILD_TOOLS_VERSION=19.0.3 19 | ANDROID_BUILD_SDK_VERSION=19 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixi-inc/Android-Device-Compatibility/8456458d7d24cee7c448d3eede934431645b36f9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 17 14:35:52 JST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.1.0/AndroidDeviceCompatibility-0.1.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixi-inc/Android-Device-Compatibility/8456458d7d24cee7c448d3eede934431645b36f9/repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.1.0/AndroidDeviceCompatibility-0.1.0.aar -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.1.0/AndroidDeviceCompatibility-0.1.0.aar.md5: -------------------------------------------------------------------------------- 1 | ee0fa9f0901ddef18fe4dce016d758ce -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.1.0/AndroidDeviceCompatibility-0.1.0.aar.sha1: -------------------------------------------------------------------------------- 1 | 53fad2d19c586c754af33836e9c49ae009b53af5 -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.1.0/AndroidDeviceCompatibility-0.1.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | jp.mixi.compatibility 6 | AndroidDeviceCompatibility 7 | 0.1.0 8 | aar 9 | 10 | 11 | com.android.support 12 | gridlayout-v7 13 | 19.0.0 14 | compile 15 | 16 | 17 | com.android.support 18 | support-v4 19 | 19.0.0 20 | compile 21 | 22 | 23 | com.android.support 24 | appcompat-v7 25 | 19.0.0 26 | compile 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.1.0/AndroidDeviceCompatibility-0.1.0.pom.md5: -------------------------------------------------------------------------------- 1 | 3e3b2aa4d41c25630efba2d12f3f82fa -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.1.0/AndroidDeviceCompatibility-0.1.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 7d6d11915320bd2741d1face077cc11424e1d627 -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.2.0/AndroidDeviceCompatibility-0.2.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixi-inc/Android-Device-Compatibility/8456458d7d24cee7c448d3eede934431645b36f9/repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.2.0/AndroidDeviceCompatibility-0.2.0.aar -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.2.0/AndroidDeviceCompatibility-0.2.0.aar.md5: -------------------------------------------------------------------------------- 1 | 5f9f863806560638f024cb9cb160aae5 -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.2.0/AndroidDeviceCompatibility-0.2.0.aar.sha1: -------------------------------------------------------------------------------- 1 | 3c558bdca9dd169aa3f640268acbcc3e9d17a502 -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.2.0/AndroidDeviceCompatibility-0.2.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | jp.mixi.compatibility 6 | AndroidDeviceCompatibility 7 | 0.2.0 8 | aar 9 | 10 | 11 | com.android.support 12 | gridlayout-v7 13 | 19.0.0 14 | compile 15 | 16 | 17 | com.android.support 18 | support-v4 19 | 19.0.0 20 | compile 21 | 22 | 23 | com.android.support 24 | appcompat-v7 25 | 19.0.0 26 | compile 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.2.0/AndroidDeviceCompatibility-0.2.0.pom.md5: -------------------------------------------------------------------------------- 1 | d5e8b4314ff74d29ccc04d1571fa8b0e -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/0.2.0/AndroidDeviceCompatibility-0.2.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 6ad7f4af5a76924dbd0155a90259b739d399b4ca -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | jp.mixi.compatibility 4 | AndroidDeviceCompatibility 5 | 0.1.0 6 | 7 | 8 | 0.1.0 9 | 0.2.0 10 | 11 | 20140117053300 12 | 13 | 14 | -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | 6feb98fbe8f9012647e8134afab852a4 -------------------------------------------------------------------------------- /repository/jp/mixi/compatibility/AndroidDeviceCompatibility/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | 383d2265206aaa7103ca986332108648d42e088a -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':AndroidDeviceCompatibility' 2 | -------------------------------------------------------------------------------- /wait_for_emulator: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bootanim="" 4 | failcounter=0 5 | until [[ "$bootanim" =~ "stopped" ]]; do 6 | bootanim=`adb -e shell getprop init.svc.bootanim 2>&1` 7 | echo "$bootanim" 8 | if [[ "$bootanim" =~ "not found" ]]; then 9 | let "failcounter += 1" 10 | if [[ $failcounter -gt 3 ]]; then 11 | echo "Failed to start emulator" 12 | exit 1 13 | fi 14 | fi 15 | sleep 1 16 | done 17 | echo "Done" 18 | -------------------------------------------------------------------------------- /wait_for_simulator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bootanim="" 4 | failcounter=0 5 | until [[ "$bootanim" =~ "stopped" ]]; do 6 | bootanim=`adb -e shell getprop init.svc.bootanim 2>&1` 7 | echo "$bootanim" 8 | if [[ "$bootanim" =~ "not found" ]]; then 9 | let "failcounter += 1" 10 | if [[ $failcounter -gt 3 ]]; then 11 | echo "Failed to start emulator" 12 | exit 1 13 | fi 14 | fi 15 | sleep 1 16 | done 17 | echo "Done" 18 | --------------------------------------------------------------------------------