diff --git a/app/build.gradle b/app/build.gradle index 4fe16246..5407b246 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "com.unity3d.ads.example" minSdkVersion 14 targetSdkVersion 26 - versionCode = 3420 - versionName = "3.4.2" + versionCode = 3460 + versionName = "3.4.6" } flavorDimensions "arEnabled" diff --git a/unity-ads/build.gradle b/unity-ads/build.gradle index 8b8850a4..3193e8c8 100644 --- a/unity-ads/build.gradle +++ b/unity-ads/build.gradle @@ -16,7 +16,7 @@ ext { siteUrl = 'https://github.com/Unity-Technologies/unity-ads-android' gitUrl = 'https://github.com/Unity-Technologies/unity-ads-android.git' - libraryVersion = '3.4.2' + libraryVersion = '3.4.6' developerId = 'sbankhead' developerName = 'Steven Bankhead' @@ -50,8 +50,8 @@ android { All SDK with version numbers with last two digits >= 50 will be treated as China SDK for filtering in the backend. */ - versionCode = 3420 - versionName = "3.4.2" + versionCode = 3460 + versionName = "3.4.6" setProperty("archivesBaseName", "unity-ads") diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/LegacyTestSuite.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/LegacyTestSuite.java index 3062fcb3..e1d8a491 100644 --- a/unity-ads/src/androidTest/java/com/unity3d/ads/test/LegacyTestSuite.java +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/LegacyTestSuite.java @@ -14,7 +14,6 @@ import com.unity3d.ads.test.legacy.LifecycleListenerTest; import com.unity3d.ads.test.legacy.MetaDataTest; import com.unity3d.ads.test.legacy.NativeCallbackTest; -import com.unity3d.ads.test.legacy.PackageManagerTest; import com.unity3d.ads.test.legacy.PlacementTest; import com.unity3d.ads.test.legacy.PreferencesTest; import com.unity3d.ads.test.legacy.PublicApiTest; @@ -52,7 +51,6 @@ InvocationTest.class, MetaDataTest.class, NativeCallbackTest.class, - PackageManagerTest.class, PlacementTest.class, PublicApiTest.class, RequestTest.class, diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/DeviceTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/DeviceTest.java index a97b6297..593e9dc1 100644 --- a/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/DeviceTest.java +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/DeviceTest.java @@ -55,20 +55,6 @@ public void testAndroidId () { assertTrue("AndroidID length should be 8 chars or more (64bit)", Device.getAndroidId().length() >= 8); } - @Test - public void testIsAppInstalled () { - List> installedPackages = Device.getInstalledPackages(false); - assertNotNull("Installed packages should not be null", installedPackages); - assertTrue("Installed packages contained: " + installedPackages.get(0).get("name") + " so it should also be installed", Device.isAppInstalled((String)installedPackages.get(0).get("name"))); - } - - @Test - public void testInstalledPackages () { - List> installedPackages = Device.getInstalledPackages(false); - assertNotNull("Installed packages should not be null", installedPackages); - assertTrue("Installed packages should contain at least 1 entry", installedPackages.size() > 0); - } - @Test public void testIsActiveNetworkConnected () { assertTrue("Active network should be connected", Device.isActiveNetworkConnected()); diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/InitializeThreadTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/InitializeThreadTest.java index 0e1cdc8a..fa794e87 100644 --- a/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/InitializeThreadTest.java +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/InitializeThreadTest.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.lang.reflect.Constructor; import java.net.MalformedURLException; import java.net.URISyntaxException; @@ -244,4 +245,17 @@ public void testInitializeStateRetry() { assertFalse("Init state retry test: retry delay is less than four seconds (should be five seconds)", (endTime - startTime) < 4000); assertFalse("Init state retry test: retry delay is greater than six seconds (should be five seconds)", (endTime - startTime) > 6000); } + + @Test() + public void testInitializeThreadRunThrowOOMError() throws Exception { + Constructor constructor = InitializeThread.class.getDeclaredConstructor(Class.forName("com.unity3d.services.core.configuration.InitializeThread$InitializeState")); + constructor.setAccessible(true); + InitializeThread initializeThread = constructor.newInstance(new InitializeThread.InitializeStateCreate(null, null) { + @Override + public InitializeThread.InitializeStateCreate execute() { + throw new OutOfMemoryError("This should get caught automatically"); + } + }); + initializeThread.run(); + } } \ No newline at end of file diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/PackageManagerTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/PackageManagerTest.java deleted file mode 100644 index c40988f4..00000000 --- a/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/PackageManagerTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.unity3d.ads.test.legacy; - -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.support.test.runner.AndroidJUnit4; -import android.test.mock.MockContext; -import android.test.mock.MockPackageManager; - -import com.unity3d.services.core.device.Device; -import com.unity3d.services.core.properties.ClientProperties; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -@RunWith(AndroidJUnit4.class) -public class PackageManagerTest { - private final String existingPkg = "com.example.package.exists"; - private final String nonExistingPkg = "com.example.package.does.not.exist"; - - @Before - public void setup() { - ClientProperties.setApplicationContext(new MockContext() { - @Override - public PackageManager getPackageManager() { - return new TestPackageManager(); - } - }); - } - - @Test - public void testIsAppInstalled() { - assertTrue("Package manager test: existing app was not found in installed apps", Device.isAppInstalled(existingPkg)); - assertFalse("Package manager test: non-exisiting app was found in installed apps", Device.isAppInstalled(nonExistingPkg)); - } - - private class TestPackageManager extends MockPackageManager { - @Override - public PackageInfo getPackageInfo(String packageName, int flags) { - if(existingPkg.equals(packageName)) { - PackageInfo info = new PackageInfo(); - info.packageName = existingPkg; - return info; - } else { - return null; - } - } - } -} \ No newline at end of file diff --git a/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/UtilitiesTest.java b/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/UtilitiesTest.java index 45484995..3ddd6611 100644 --- a/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/UtilitiesTest.java +++ b/unity-ads/src/androidTest/java/com/unity3d/ads/test/legacy/UtilitiesTest.java @@ -113,12 +113,27 @@ public void testSha256Stream() throws Exception { } @Test - public void testReadFileBytes() throws Exception { - byte[] large_data = Utilities.readFileBytes(new File(SdkProperties.getCacheDirectory() + "/" + SdkProperties.getCacheFilePrefix() + "testfile_large.dat")); + public void testReadFileBytesNull() throws Exception { + byte[] null_data = Utilities.readFileBytes(new File(SdkProperties.getCacheDirectory() + "/" + SdkProperties.getCacheFilePrefix() + null)); + assertEquals("Incorrectly read bytes of null file", null, null_data); + } + + @Test + public void testReadFileBytesNonExistent() throws Exception { + byte[] fake_data = Utilities.readFileBytes(new File(SdkProperties.getCacheDirectory() + "/" + SdkProperties.getCacheFilePrefix() + "fake_file.dat")); + assertEquals("Incorrect read bytes of non-existent file", null, fake_data); + } + + @Test + public void testReadFileBytesSmallFile() throws Exception { byte[] small_data = Utilities.readFileBytes(new File(SdkProperties.getCacheDirectory() + "/" + SdkProperties.getCacheFilePrefix() + "testfile_small.dat")); + assertEquals("Incorrect read small file data size", 1024, small_data.length); + } + @Test + public void testReadFileBytesLargeFile() throws Exception { + byte[] large_data = Utilities.readFileBytes(new File(SdkProperties.getCacheDirectory() + "/" + SdkProperties.getCacheFilePrefix() + "testfile_large.dat")); assertEquals("Incorrect read large file data size", 56789, large_data.length); - assertEquals("Incorrect read small file data size", 1024, small_data.length); } public void assertHash(int size, String sha256) throws Exception { diff --git a/unity-ads/src/main/java/com/unity3d/services/ads/webplayer/WebPlayerView.java b/unity-ads/src/main/java/com/unity3d/services/ads/webplayer/WebPlayerView.java index c4157875..7f95de6e 100644 --- a/unity-ads/src/main/java/com/unity3d/services/ads/webplayer/WebPlayerView.java +++ b/unity-ads/src/main/java/com/unity3d/services/ads/webplayer/WebPlayerView.java @@ -475,9 +475,13 @@ public void onLoadResource(WebView view, String url) { @TargetApi(14) @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - if (shouldCallSuper("onReceivedSslError")) { - super.onReceivedSslError(view, handler, error); - } + // Don't call shouldCallSuper("onReceivedSslError") to ensure + // we always rely on the default behavior. Otherwise it is + // possible Google might reject the app as an unsafe implementation. + // https://support.google.com/faqs/answer/7071387?hl=en + super.onReceivedSslError(view, handler, error); + DeviceLog.error("Received SSL error for '%s': %s", error.getUrl(), error.toString()); + if (shouldSendEvent("onReceivedSslError")) { String url = ""; if (error != null) { diff --git a/unity-ads/src/main/java/com/unity3d/services/core/api/DeviceInfo.java b/unity-ads/src/main/java/com/unity3d/services/core/api/DeviceInfo.java index 1781e457..ba3ed7cb 100644 --- a/unity-ads/src/main/java/com/unity3d/services/core/api/DeviceInfo.java +++ b/unity-ads/src/main/java/com/unity3d/services/core/api/DeviceInfo.java @@ -1,13 +1,8 @@ package com.unity3d.services.core.api; -import android.Manifest; -import android.annotation.SuppressLint; -import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.hardware.Sensor; -import android.os.Build; -import android.telephony.TelephonyManager; import android.util.SparseArray; import com.unity3d.services.core.device.Device; @@ -147,11 +142,6 @@ public static void getNetworkCountryISO(WebViewCallback callback) { callback.invoke(Device.getNetworkCountryISO()); } - @WebViewExposed - public static void isAppInstalled(String pkgname, WebViewCallback callback) { - callback.invoke(Device.isAppInstalled(pkgname)); - } - @WebViewExposed public static void isRooted(WebViewCallback callback) { callback.invoke(Device.isRooted()); @@ -167,13 +157,6 @@ public static void isAdbEnabled(WebViewCallback callback) { } } - @WebViewExposed - public static void getInstalledPackages(boolean md5, WebViewCallback callback) { - List> installedPackages = Device.getInstalledPackages(md5); - JSONArray packageJson = new JSONArray(installedPackages); - callback.invoke(packageJson); - } - @WebViewExposed public static void getPackageInfo(String packageName, WebViewCallback callback) { if (ClientProperties.getApplicationContext() != null) { @@ -591,56 +574,5 @@ public static void getBuildId(WebViewCallback callback) { public static void getBuildVersionIncremental(WebViewCallback callback) { callback.invoke(Device.getBuildVersionIncremental()); } - - @WebViewExposed - @SuppressLint("MissingPermission") - public static void getDeviceIdWithSlot(final Integer slotIndex, WebViewCallback callback) { - getDeviceIdCommon(slotIndex, callback); - } - - @WebViewExposed - public static void getDeviceId(WebViewCallback callback) { - getDeviceIdCommon(null, callback); - - } - - @SuppressLint("MissingPermission") - private static void getDeviceIdCommon(final Integer slotIndex, WebViewCallback callback) { - if (ClientProperties.getApplicationContext() == null) { - callback.error(DeviceError.APPLICATION_CONTEXT_NULL); - return; - } - - if (ClientProperties.getApplicationContext().checkCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { - callback.error(PermissionsError.PERMISSION_NOT_GRANTED); - return; - } - - TelephonyManager mTelephony = (TelephonyManager) ClientProperties.getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE); - - if (mTelephony != null) { - // check api version - // getDeviceId was deprecated in API level 26 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (slotIndex == null) { - callback.invoke(mTelephony.getImei()); - } else { - callback.invoke(mTelephony.getImei(slotIndex)); - } - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (slotIndex == null) { - callback.invoke(mTelephony.getDeviceId()); - } else { - callback.invoke(mTelephony.getDeviceId(slotIndex)); - } - } else if (slotIndex == null) { - // getDeviceId() is supported for API level <= 23 but getDeviceId(int) is not - callback.invoke(mTelephony.getDeviceId()); - } else { - callback.error(DeviceError.API_LEVEL_ERROR, Build.VERSION.SDK_INT); - } - } - - } } diff --git a/unity-ads/src/main/java/com/unity3d/services/core/api/Permissions.java b/unity-ads/src/main/java/com/unity3d/services/core/api/Permissions.java index ae8f090a..66915f90 100644 --- a/unity-ads/src/main/java/com/unity3d/services/core/api/Permissions.java +++ b/unity-ads/src/main/java/com/unity3d/services/core/api/Permissions.java @@ -22,8 +22,7 @@ enum PermissionsError { COULDNT_GET_PERMISSIONS, NO_REQUESTED_PERMISSIONS, ERROR_CHECKING_PERMISSION, - ERROR_REQUESTING_PERMISSIONS, - PERMISSION_NOT_GRANTED + ERROR_REQUESTING_PERMISSIONS } public class Permissions { diff --git a/unity-ads/src/main/java/com/unity3d/services/core/configuration/InitializeThread.java b/unity-ads/src/main/java/com/unity3d/services/core/configuration/InitializeThread.java index 64c196d1..ff74e3c0 100644 --- a/unity-ads/src/main/java/com/unity3d/services/core/configuration/InitializeThread.java +++ b/unity-ads/src/main/java/com/unity3d/services/core/configuration/InitializeThread.java @@ -15,8 +15,6 @@ import com.unity3d.services.core.webview.WebViewApp; import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; public class InitializeThread extends Thread { @@ -31,10 +29,21 @@ private InitializeThread(InitializeState state) { @Override public void run() { - while ((_state != null && !(_state instanceof InitializeStateComplete)) && !_stopThread) { - _state = _state.execute(); + try { + while ((_state != null && !(_state instanceof InitializeStateComplete)) && !_stopThread) { + try { + _state = _state.execute(); + } catch (Exception e) { + DeviceLog.exception("Unity Ads SDK encountered an error during initialization, cancel initialization", e); + _state = new InitializeThread.InitializeStateForceReset(); + } catch (OutOfMemoryError oom) { + DeviceLog.exception("Application doesn't have enough memory to initialize Unity Ads SDK", new Exception(oom)); + _state = new InitializeThread.InitializeStateForceReset(); + } + } + } catch (OutOfMemoryError oom) { + //Do Nothing } - _thread = null; } @@ -225,7 +234,7 @@ public InitializeState execute() { try { localWebViewData = Utilities.readFileBytes(new File(SdkProperties.getLocalWebViewFile())); - } catch (IOException e) { + } catch (Exception e) { DeviceLog.debug("Unity Ads init: webapp not found in local cache: " + e.getMessage()); return new InitializeStateLoadWeb(_configuration); } @@ -237,7 +246,7 @@ public InitializeState execute() { try { webViewDataString = new String(localWebViewData, "UTF-8"); - } catch (UnsupportedEncodingException e) { + } catch (Exception e) { return new InitializeStateError("load cache", e, _configuration); } @@ -469,7 +478,7 @@ public InitializeState execute() { DeviceLog.debug("Unity Ads init: retrying in " + _delay + " seconds"); try { Thread.sleep(_delay * 1000L); - } catch(InterruptedException e) { + } catch(Exception e) { DeviceLog.exception("Init retry interrupted", e); } diff --git a/unity-ads/src/main/java/com/unity3d/services/core/device/Device.java b/unity-ads/src/main/java/com/unity3d/services/core/device/Device.java index f5ccbfc7..842ac618 100644 --- a/unity-ads/src/main/java/com/unity3d/services/core/device/Device.java +++ b/unity-ads/src/main/java/com/unity3d/services/core/device/Device.java @@ -212,56 +212,6 @@ public static boolean isActiveNetworkConnected () { return false; } - public static boolean isAppInstalled(String pkgname) { - if (ClientProperties.getApplicationContext() != null) { - PackageManager pm = ClientProperties.getApplicationContext().getPackageManager(); - - try { - PackageInfo pkgInfo = pm.getPackageInfo(pkgname, 0); - - if(pkgInfo != null && pkgInfo.packageName != null && pkgname.equals(pkgInfo.packageName)) { - return true; - } - } catch(PackageManager.NameNotFoundException e) { - return false; - } - } - - return false; - } - - public static List> getInstalledPackages(boolean hash) { - List> returnList = new ArrayList<>(); - - if (ClientProperties.getApplicationContext() != null) { - PackageManager pm = ClientProperties.getApplicationContext().getPackageManager(); - - for(PackageInfo pkg : pm.getInstalledPackages(0)) { - HashMap packageEntry = new HashMap<>(); - - if (hash) { - packageEntry.put("name", Utilities.Sha256(pkg.packageName)); - } - else { - packageEntry.put("name", pkg.packageName); - } - - if (pkg.firstInstallTime > 0) { - packageEntry.put("time", pkg.firstInstallTime); - } - - String installer = pm.getInstallerPackageName(pkg.packageName); - if (installer != null && !installer.isEmpty()) { - packageEntry.put("installer", installer); - } - - returnList.add(packageEntry); - } - } - - return returnList; - } - public static String getUniqueEventId() { return UUID.randomUUID().toString(); } diff --git a/unity-ads/src/main/java/com/unity3d/services/core/device/DeviceError.java b/unity-ads/src/main/java/com/unity3d/services/core/device/DeviceError.java index c38de571..3a9ab95c 100644 --- a/unity-ads/src/main/java/com/unity3d/services/core/device/DeviceError.java +++ b/unity-ads/src/main/java/com/unity3d/services/core/device/DeviceError.java @@ -10,6 +10,5 @@ public enum DeviceError { JSON_ERROR, COULDNT_GET_DIGEST, COULDNT_GET_FINGERPRINT, - COULDNT_GET_ADB_STATUS, - API_LEVEL_ERROR + COULDNT_GET_ADB_STATUS } diff --git a/unity-ads/src/main/java/com/unity3d/services/core/misc/Utilities.java b/unity-ads/src/main/java/com/unity3d/services/core/misc/Utilities.java index 6b19085c..15c870d4 100644 --- a/unity-ads/src/main/java/com/unity3d/services/core/misc/Utilities.java +++ b/unity-ads/src/main/java/com/unity3d/services/core/misc/Utilities.java @@ -1,5 +1,6 @@ package com.unity3d.services.core.misc; +import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -8,13 +9,12 @@ import org.json.JSONException; import org.json.JSONObject; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Iterator; @@ -131,27 +131,31 @@ public static boolean writeFile (File fileToWrite, String content) { } public static byte[] readFileBytes(File file) throws IOException { - if (file == null) { + + if (file == null || !file.exists()) { return null; } - InputStream inputStream = new FileInputStream(file); + // If Device API Level is 26+, utilize built in functionality + if (Build.VERSION.SDK_INT >= 26) { + return Files.readAllBytes(file.toPath()); + } - byte[] buffer = new byte[(int)file.length()]; - int offset = 0; - int read; - int defaultBufferSize = 4096; - int readAmount = file.length() < defaultBufferSize ? (int)file.length() : defaultBufferSize; + InputStream inputStream = null; + byte[] buffer; - while ((read = inputStream.read(buffer, offset, readAmount)) > 0) { - offset += read; - if (file.length() - offset < defaultBufferSize) { - readAmount = (int)file.length() - offset; + try { + inputStream = new FileInputStream(file); + buffer = new byte[(int) file.length()]; + int readBytes = inputStream.read(buffer); + if (readBytes != buffer.length) { + throw new IOException("Failed to read all bytes from input file path: " + file.getPath()); + } + } finally { + if (inputStream != null) { + inputStream.close(); } } - - inputStream.close(); - return buffer; }