handle installing OTA files separately from generic .zip files
It is valid to include .zip files in a repo, but only OTA ZIP files should be installed into the OTA dir.
This commit is contained in:
		
							parent
							
								
									5a0092d42e
								
							
						
					
					
						commit
						4bb158ef77
					
				| @ -17,14 +17,18 @@ import androidx.annotation.Nullable; | ||||
| import com.fasterxml.jackson.annotation.JacksonInject; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| import org.fdroid.fdroid.BuildConfig; | ||||
| import org.fdroid.fdroid.Utils; | ||||
| import org.fdroid.fdroid.data.Schema.ApkTable.Cols; | ||||
| import org.fdroid.fdroid.installer.ApkCache; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.Collections; | ||||
| import java.util.Date; | ||||
| import java.util.HashSet; | ||||
| import java.util.Locale; | ||||
| import java.util.zip.ZipFile; | ||||
| 
 | ||||
| /** | ||||
|  * Represents a single package of an application. This represents one particular | ||||
| @ -551,10 +555,12 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the install path for a "non-apk" media file | ||||
|      * Defaults to {@link android.os.Environment#DIRECTORY_DOWNLOADS} | ||||
|      * Get the install path for a "non-apk" media file, with special cases for | ||||
|      * files that can be usefully installed without PrivilegedExtension. | ||||
|      * Defaults to {@link android.os.Environment#DIRECTORY_DOWNLOADS}. | ||||
|      * | ||||
|      * @return the install path for this {@link Apk} | ||||
|      * @link <a href="https://source.android.com/devices/tech/ota/nonab/inside_packages">Inside OTA Packages</a> | ||||
|      */ | ||||
| 
 | ||||
|     public File getMediaInstallPath(Context context) { | ||||
| @ -579,11 +585,22 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable { | ||||
|         } else if ("video".equals(topLevelType)) { | ||||
|             path = Environment.getExternalStoragePublicDirectory( | ||||
|                     Environment.DIRECTORY_MOVIES); | ||||
|             // TODO support OsmAnd map files, other map apps? | ||||
|             //} else if (mimeTypeMap.hasExtension("map")) {  // OsmAnd map files | ||||
|             //} else if (this.apkName.matches(".*.ota_[0-9]*.zip")) {  // Over-The-Air update ZIP files | ||||
|         } else if (this.apkName.endsWith(".zip")) {  // Over-The-Air update ZIP files | ||||
|             path = new File(context.getApplicationInfo().dataDir + "/ota"); | ||||
|         } else if ("zip".equals(fileExtension)) { | ||||
|             try { | ||||
|                 File cachedFile = ApkCache.getApkDownloadPath(context, this.getCanonicalUrl()); | ||||
|                 ZipFile zipFile = new ZipFile(cachedFile); | ||||
|                 if (zipFile.getEntry("META-INF/com/google/android/update-binary") != null) { | ||||
|                     // Over-The-Air update ZIP files | ||||
|                     return new File(context.getApplicationInfo().dataDir + "/ota"); | ||||
|                 } | ||||
|             } catch (IOException e) { | ||||
|                 // this should happen when running isMediaInstalled() and the file isn't installed | ||||
|                 // other cases are probably bugs | ||||
|                 if (BuildConfig.DEBUG) e.printStackTrace(); | ||||
|             } | ||||
|             return path; | ||||
|         } else if ("apk".equals(fileExtension)) { | ||||
|             throw new IllegalStateException("APKs should not be handled in the media install path!"); | ||||
|         } | ||||
|         return path; | ||||
|     } | ||||
|  | ||||
							
								
								
									
										76
									
								
								app/src/test/java/org/fdroid/fdroid/data/ApkTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								app/src/test/java/org/fdroid/fdroid/data/ApkTest.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| package org.fdroid.fdroid.data; | ||||
| 
 | ||||
| import android.content.ContextWrapper; | ||||
| import android.os.Environment; | ||||
| import android.util.Log; | ||||
| import android.webkit.MimeTypeMap; | ||||
| import androidx.test.core.app.ApplicationProvider; | ||||
| import org.apache.commons.io.FileUtils; | ||||
| import org.fdroid.fdroid.installer.ApkCache; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.robolectric.RobolectricTestRunner; | ||||
| import org.robolectric.Shadows; | ||||
| import org.robolectric.shadows.ShadowLog; | ||||
| import org.robolectric.shadows.ShadowMimeTypeMap; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| @RunWith(RobolectricTestRunner.class) | ||||
| public class ApkTest { | ||||
|     public static final String TAG = "ApkTest"; | ||||
| 
 | ||||
|     private static ContextWrapper context; | ||||
| 
 | ||||
|     @Before | ||||
|     public final void setUp() { | ||||
|         context = ApplicationProvider.getApplicationContext(); | ||||
|         ShadowMimeTypeMap mimeTypeMap = Shadows.shadowOf(MimeTypeMap.getSingleton()); | ||||
|         mimeTypeMap.addExtensionMimeTypMapping("apk", "application/vnd.android.package-archive"); | ||||
|         mimeTypeMap.addExtensionMimeTypMapping("obf", "application/octet-stream"); | ||||
|         mimeTypeMap.addExtensionMimeTypMapping("zip", "application/zip"); | ||||
|         ShadowLog.stream = System.out; | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalStateException.class) | ||||
|     public void testGetMediaInstallPathWithApk() { | ||||
|         Apk apk = new Apk(); | ||||
|         apk.apkName = "test.apk"; | ||||
|         apk.repoAddress = "https://example.com/fdroid/repo"; | ||||
|         assertTrue(apk.isApk()); | ||||
|         apk.getMediaInstallPath(context); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetMediaInstallPathWithOta() throws IOException { | ||||
|         Apk apk = new Apk(); | ||||
|         apk.apkName = "org.fdroid.fdroid.privileged.ota_2110.zip"; | ||||
|         apk.repoAddress = "https://example.com/fdroid/repo"; | ||||
|         assertFalse(apk.isApk()); | ||||
|         copyResourceFileToCache(apk); | ||||
|         File path = apk.getMediaInstallPath(context); | ||||
|         assertEquals(new File(context.getApplicationInfo().dataDir + "/ota"), path); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetMediaInstallPathWithObfZip() throws IOException { | ||||
|         Apk apk = new Apk(); | ||||
|         apk.apkName = "Norway_bouvet_europe_2.obf.zip"; | ||||
|         apk.repoAddress = "https://example.com/fdroid/repo"; | ||||
|         assertFalse(apk.isApk()); | ||||
|         copyResourceFileToCache(apk); | ||||
|         File path = apk.getMediaInstallPath(context); | ||||
|         assertEquals(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), path); | ||||
|     } | ||||
| 
 | ||||
|     private void copyResourceFileToCache(Apk apk) throws IOException { | ||||
|         FileUtils.copyInputStreamToFile(getClass().getClassLoader().getResource(apk.apkName).openStream(), | ||||
|                 ApkCache.getApkDownloadPath(context, apk.getCanonicalUrl())); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,58 @@ | ||||
| package org.fdroid.fdroid.installer; | ||||
| 
 | ||||
| import android.content.ContextWrapper; | ||||
| import android.util.Log; | ||||
| import androidx.test.core.app.ApplicationProvider; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.robolectric.RobolectricTestRunner; | ||||
| import org.robolectric.shadows.ShadowLog; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| @RunWith(RobolectricTestRunner.class) | ||||
| public class ApkCacheTest { | ||||
|     private static final String TAG = "ApkCacheTest"; | ||||
| 
 | ||||
|     private ContextWrapper context; | ||||
|     private File cacheDir; | ||||
| 
 | ||||
|     @Before | ||||
|     public final void setUp() { | ||||
|         context = ApplicationProvider.getApplicationContext(); | ||||
|         cacheDir = ApkCache.getApkCacheDir(context); | ||||
|         ShadowLog.stream = System.out; | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetApkCacheDir() { | ||||
|         Log.i(TAG, "path: " + cacheDir); | ||||
|         assertTrue("Must be full path", cacheDir.isAbsolute()); | ||||
|         assertTrue("Must be a directory", cacheDir.isDirectory()); | ||||
|         assertTrue("Must be writable", cacheDir.canWrite()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetApkDownloadPath() { | ||||
|         assertEquals("Should be in folder based on repo hostname", | ||||
|                 new File(cacheDir, "f-droid.org--1/org.fdroid.fdroid_1008000.apk"), | ||||
|                 ApkCache.getApkDownloadPath(context, | ||||
|                         "https://f-droid.org/repo/org.fdroid.fdroid_1008000.apk")); | ||||
|         assertEquals("Should be in folder based on repo hostname with port number", | ||||
|                 new File(cacheDir, "192.168.234.12-8888/sun.bob.leela_2.apk"), | ||||
|                 ApkCache.getApkDownloadPath(context, | ||||
|                         "http://192.168.234.12:8888/fdroid/repo/sun.bob.leela_2.apk")); | ||||
|         assertEquals("Should work for OTA files also", | ||||
|                 new File(cacheDir, "f-droid.org--1/org.fdroid.fdroid.privileged.ota_2110.zip"), | ||||
|                 ApkCache.getApkDownloadPath(context, | ||||
|                         "http://f-droid.org/fdroid/repo/org.fdroid.fdroid.privileged.ota_2110.zip")); | ||||
|         assertEquals("Should work for ZIP files also", | ||||
|                 new File(cacheDir, "example.com--1/Norway_bouvet_europe_2.obf.zip"), | ||||
|                 ApkCache.getApkDownloadPath(context, | ||||
|                         "https://example.com/fdroid/repo/Norway_bouvet_europe_2.obf.zip")); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								app/src/test/resources/Norway_bouvet_europe_2.obf.zip
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/src/test/resources/Norway_bouvet_europe_2.obf.zip
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/src/test/resources/org.fdroid.fdroid.privileged.ota_2110.zip
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/src/test/resources/org.fdroid.fdroid.privileged.ota_2110.zip
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Hans-Christoph Steiner
						Hans-Christoph Steiner