diff options
17 files changed, 384 insertions, 93 deletions
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java index 6454367..3a2c21b 100644 --- a/core/java/android/app/TimePickerDialog.java +++ b/core/java/android/app/TimePickerDialog.java @@ -134,6 +134,9 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, mTimePicker.getCurrentMinute()); } break; + case BUTTON_NEGATIVE: + cancel(); + break; } } diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index fe47f5b..0a724a1 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -17,9 +17,8 @@ package android.os; import android.system.ErrnoException; -import android.text.TextUtils; import android.system.Os; -import android.system.OsConstants; +import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -403,20 +402,89 @@ public class FileUtils { return success; } + private static boolean isValidExtFilenameChar(char c) { + switch (c) { + case '\0': + case '/': + return false; + default: + return true; + } + } + /** - * Assert that given filename is valid on ext4. + * Check if given filename is valid for an ext4 filesystem. */ public static boolean isValidExtFilename(String name) { + return (name != null) && name.equals(buildValidExtFilename(name)); + } + + /** + * Mutate the given filename to make it valid for an ext4 filesystem, + * replacing any invalid characters with "_". + */ + public static String buildValidExtFilename(String name) { if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { - return false; + return "(invalid)"; } + final StringBuilder res = new StringBuilder(name.length()); for (int i = 0; i < name.length(); i++) { final char c = name.charAt(i); - if (c == '\0' || c == '/') { + if (isValidExtFilenameChar(c)) { + res.append(c); + } else { + res.append('_'); + } + } + return res.toString(); + } + + private static boolean isValidFatFilenameChar(char c) { + if ((0x00 <= c && c <= 0x1f)) { + return false; + } + switch (c) { + case '"': + case '*': + case '/': + case ':': + case '<': + case '>': + case '?': + case '\\': + case '|': + case 0x7F: return false; + default: + return true; + } + } + + /** + * Check if given filename is valid for a FAT filesystem. + */ + public static boolean isValidFatFilename(String name) { + return (name != null) && name.equals(buildValidFatFilename(name)); + } + + /** + * Mutate the given filename to make it valid for a FAT filesystem, + * replacing any invalid characters with "_". + */ + public static String buildValidFatFilename(String name) { + if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { + return "(invalid)"; + } + final StringBuilder res = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + final char c = name.charAt(i); + if (isValidFatFilenameChar(c)) { + res.append(c); + } else { + res.append('_'); } } - return true; + return res.toString(); } public static String rewriteAfterRename(File beforeDir, File afterDir, String path) { diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index 93e68eb..5c9e813 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -180,6 +180,51 @@ public class FileUtilsTest extends AndroidTestCase { assertDirContents("file1", "file2"); } + public void testValidExtFilename() throws Exception { + assertTrue(FileUtils.isValidExtFilename("a")); + assertTrue(FileUtils.isValidExtFilename("foo.bar")); + assertTrue(FileUtils.isValidExtFilename("foo bar.baz")); + assertTrue(FileUtils.isValidExtFilename("foo.bar.baz")); + assertTrue(FileUtils.isValidExtFilename(".bar")); + assertTrue(FileUtils.isValidExtFilename("foo~!@#$%^&*()_[]{}+bar")); + + assertFalse(FileUtils.isValidExtFilename(null)); + assertFalse(FileUtils.isValidExtFilename(".")); + assertFalse(FileUtils.isValidExtFilename("../foo")); + assertFalse(FileUtils.isValidExtFilename("/foo")); + + assertEquals(".._foo", FileUtils.buildValidExtFilename("../foo")); + assertEquals("_foo", FileUtils.buildValidExtFilename("/foo")); + assertEquals("foo_bar", FileUtils.buildValidExtFilename("foo\0bar")); + assertEquals(".foo", FileUtils.buildValidExtFilename(".foo")); + assertEquals("foo.bar", FileUtils.buildValidExtFilename("foo.bar")); + } + + public void testValidFatFilename() throws Exception { + assertTrue(FileUtils.isValidFatFilename("a")); + assertTrue(FileUtils.isValidFatFilename("foo bar.baz")); + assertTrue(FileUtils.isValidFatFilename("foo.bar.baz")); + assertTrue(FileUtils.isValidFatFilename(".bar")); + assertTrue(FileUtils.isValidFatFilename("foo.bar")); + assertTrue(FileUtils.isValidFatFilename("foo bar")); + assertTrue(FileUtils.isValidFatFilename("foo+bar")); + assertTrue(FileUtils.isValidFatFilename("foo,bar")); + + assertFalse(FileUtils.isValidFatFilename("foo*bar")); + assertFalse(FileUtils.isValidFatFilename("foo?bar")); + assertFalse(FileUtils.isValidFatFilename("foo<bar")); + assertFalse(FileUtils.isValidFatFilename(null)); + assertFalse(FileUtils.isValidFatFilename(".")); + assertFalse(FileUtils.isValidFatFilename("../foo")); + assertFalse(FileUtils.isValidFatFilename("/foo")); + + assertEquals(".._foo", FileUtils.buildValidFatFilename("../foo")); + assertEquals("_foo", FileUtils.buildValidFatFilename("/foo")); + assertEquals(".foo", FileUtils.buildValidFatFilename(".foo")); + assertEquals("foo.bar", FileUtils.buildValidFatFilename("foo.bar")); + assertEquals("foo_bar__baz", FileUtils.buildValidFatFilename("foo?bar**baz")); + } + private void touch(String name, long age) throws Exception { final File file = new File(mDir, name); file.createNewFile(); diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd index 448dcda..063084d 100644 --- a/docs/html/about/dashboards/index.jd +++ b/docs/html/about/dashboards/index.jd @@ -57,7 +57,7 @@ Platform Versions</a>.</p> </div> -<p style="clear:both"><em>Data collected during a 7-day period ending on November 3, 2014. +<p style="clear:both"><em>Data collected during a 7-day period ending on December 1, 2014. <br/>Any versions with less than 0.1% distribution are not shown.</em> </p> @@ -88,7 +88,7 @@ Screens</a>.</p> </div> -<p style="clear:both"><em>Data collected during a 7-day period ending on November 3, 2014. +<p style="clear:both"><em>Data collected during a 7-day period ending on December 1, 2014. <br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p> @@ -108,7 +108,8 @@ support for any lower version (for example, support for version 2.0 also implies <img alt="" style="float:right" -src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0&chd=t%3A74.7%2C25.3&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=400x250" /> +src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0&chf=bg%2Cs%2C00000000&chd=t%3A72.2%2C27.8&chco=c4df9b%2C6fad0c&cht=p&chs=400x250" /> + <p>To declare which version of OpenGL ES your application requires, you should use the {@code android:glEsVersion} attribute of the <a @@ -126,17 +127,17 @@ uses.</p> </tr> <tr> <td>2.0</td> -<td>74.7%</td> +<td>72.2%</td> </tr> <tr> <td>3.0</td> -<td>25.3%</td> +<td>27.8%</td> </tr> </table> -<p style="clear:both"><em>Data collected during a 7-day period ending on November 3, 2014</em></p> +<p style="clear:both"><em>Data collected during a 7-day period ending on December 1, 2014</em></p> @@ -154,42 +155,42 @@ uses.</p> var VERSION_DATA = [ { - "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat&chd=t%3A0.6%2C9.8%2C8.5%2C50.9%2C30.2&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=500x250", + "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat&chf=bg%2Cs%2C00000000&chd=t%3A0.5%2C9.1%2C7.8%2C48.7%2C33.9&chco=c4df9b%2C6fad0c&cht=p&chs=500x250", "data": [ { "api": 8, "name": "Froyo", - "perc": "0.6" + "perc": "0.5" }, { "api": 10, "name": "Gingerbread", - "perc": "9.8" + "perc": "9.1" }, { "api": 15, "name": "Ice Cream Sandwich", - "perc": "8.5" + "perc": "7.8" }, { "api": 16, "name": "Jelly Bean", - "perc": "22.8" + "perc": "21.3" }, { "api": 17, "name": "Jelly Bean", - "perc": "20.8" + "perc": "20.4" }, { "api": 18, "name": "Jelly Bean", - "perc": "7.3" + "perc": "7.0" }, { "api": 19, "name": "KitKat", - "perc": "30.2" + "perc": "33.9" } ] } @@ -203,27 +204,28 @@ var SCREEN_DATA = "Large": { "hdpi": "0.6", "ldpi": "0.5", - "mdpi": "4.5", - "tvdpi": "1.9", + "mdpi": "4.6", + "tvdpi": "2.0", "xhdpi": "0.6" }, "Normal": { - "hdpi": "36.6", - "mdpi": "9.9", - "xhdpi": "18.9", - "xxhdpi": "16.0" + "hdpi": "36.9", + "mdpi": "9.4", + "tvdpi": "0.2", + "xhdpi": "18.8", + "xxhdpi": "16.3" }, "Small": { - "ldpi": "5.8" + "ldpi": "5.4" }, "Xlarge": { "hdpi": "0.3", - "mdpi": "3.9", - "xhdpi": "0.5" + "mdpi": "3.8", + "xhdpi": "0.6" } }, - "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chd=t%3A6.3%2C18.3%2C1.9%2C37.5%2C20.0%2C16.0&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=400x250", - "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chd=t%3A4.7%2C8.1%2C81.4%2C5.8&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=400x250" + "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chf=bg%2Cs%2C00000000&chd=t%3A5.9%2C17.8%2C2.2%2C37.8%2C20.0%2C16.3&chco=c4df9b%2C6fad0c&cht=p&chs=400x250", + "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chf=bg%2Cs%2C00000000&chd=t%3A4.7%2C8.3%2C81.6%2C5.4&chco=c4df9b%2C6fad0c&cht=p&chs=400x250" } ]; diff --git a/docs/html/design/media/wear/ContextualExample.006.png b/docs/html/design/media/wear/ContextualExample.006.png Binary files differindex 7c3da57a..e680afb 100644 --- a/docs/html/design/media/wear/ContextualExample.006.png +++ b/docs/html/design/media/wear/ContextualExample.006.png diff --git a/docs/html/design/media/wear/ContextualExample.006_2x.png b/docs/html/design/media/wear/ContextualExample.006_2x.png Binary files differindex 319530d..ee4087e 100644 --- a/docs/html/design/media/wear/ContextualExample.006_2x.png +++ b/docs/html/design/media/wear/ContextualExample.006_2x.png diff --git a/docs/html/design/wear/context.jd b/docs/html/design/wear/context.jd index 2e66532..688806f 100644 --- a/docs/html/design/wear/context.jd +++ b/docs/html/design/wear/context.jd @@ -131,7 +131,7 @@ app.</p> <div class="slide"> <h2>Zoo</h2> -<p>Notifies visitors when the penguins are going to be fed! +<p>Notifies visitors when the penguins are going to be fed. </p> <img src="{@docRoot}design/media/wear/ContextualExample.014.png" alt="" srcset="{@docRoot}design/media/wear/ContextualExample.014.png 1x, diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 80b4c2a..075f2c5 100755 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1560,13 +1560,7 @@ void OpenGLRenderer::setupDraw(bool clearLayer) { setScissorFromClip(); } - if (clearLayer) { - setStencilFromClip(); - } else { - // While clearing layer, force disable stencil buffer, since - // it's invalid to stencil-clip *during* the layer clear - mCaches.stencil.disable(); - } + setStencilFromClip(); } mDescription.reset(); diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 066acac..073d9c7 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -43,6 +43,7 @@ import android.util.Log; import android.webkit.MimeTypeMap; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -53,6 +54,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; +import java.util.Objects; public class ExternalStorageProvider extends DocumentsProvider { private static final String TAG = "ExternalStorage"; @@ -313,27 +315,19 @@ public class ExternalStorageProvider extends DocumentsProvider { @Override public String createDocument(String docId, String mimeType, String displayName) throws FileNotFoundException { + displayName = FileUtils.buildValidFatFilename(displayName); + final File parent = getFileForDocId(docId); if (!parent.isDirectory()) { throw new IllegalArgumentException("Parent document isn't a directory"); } - File file; + final File file = buildUniqueFile(parent, mimeType, displayName); if (Document.MIME_TYPE_DIR.equals(mimeType)) { - file = new File(parent, displayName); if (!file.mkdir()) { throw new IllegalStateException("Failed to mkdir " + file); } } else { - displayName = removeExtension(mimeType, displayName); - file = new File(parent, addExtension(mimeType, displayName)); - - // If conflicting file, try adding counter suffix - int n = 0; - while (file.exists() && n++ < 32) { - file = new File(parent, addExtension(mimeType, displayName + " (" + n + ")")); - } - try { if (!file.createNewFile()) { throw new IllegalStateException("Failed to touch " + file); @@ -342,11 +336,78 @@ public class ExternalStorageProvider extends DocumentsProvider { throw new IllegalStateException("Failed to touch " + file + ": " + e); } } + return getDocIdForFile(file); } + private static File buildFile(File parent, String name, String ext) { + if (TextUtils.isEmpty(ext)) { + return new File(parent, name); + } else { + return new File(parent, name + "." + ext); + } + } + + @VisibleForTesting + public static File buildUniqueFile(File parent, String mimeType, String displayName) + throws FileNotFoundException { + String name; + String ext; + + if (Document.MIME_TYPE_DIR.equals(mimeType)) { + name = displayName; + ext = null; + } else { + String mimeTypeFromExt; + + // Extract requested extension from display name + final int lastDot = displayName.lastIndexOf('.'); + if (lastDot >= 0) { + name = displayName.substring(0, lastDot); + ext = displayName.substring(lastDot + 1); + mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension( + ext.toLowerCase()); + } else { + name = displayName; + ext = null; + mimeTypeFromExt = null; + } + + if (mimeTypeFromExt == null) { + mimeTypeFromExt = "application/octet-stream"; + } + + final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType( + mimeType); + if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) { + // Extension maps back to requested MIME type; allow it + } else { + // No match; insist that create file matches requested MIME + name = displayName; + ext = extFromMimeType; + } + } + + File file = buildFile(parent, name, ext); + + // If conflicting file, try adding counter suffix + int n = 0; + while (file.exists()) { + if (n++ >= 32) { + throw new FileNotFoundException("Failed to create unique file"); + } + file = buildFile(parent, name + " (" + n + ")", ext); + } + + return file; + } + @Override public String renameDocument(String docId, String displayName) throws FileNotFoundException { + // Since this provider treats renames as generating a completely new + // docId, we're okay with letting the MIME type change. + displayName = FileUtils.buildValidFatFilename(displayName); + final File before = getFileForDocId(docId); final File after = new File(before.getParentFile(), displayName); if (after.exists()) { @@ -482,34 +543,6 @@ public class ExternalStorageProvider extends DocumentsProvider { return "application/octet-stream"; } - /** - * Remove file extension from name, but only if exact MIME type mapping - * exists. This means we can reapply the extension later. - */ - private static String removeExtension(String mimeType, String name) { - final int lastDot = name.lastIndexOf('.'); - if (lastDot >= 0) { - final String extension = name.substring(lastDot + 1).toLowerCase(); - final String nameMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mimeType.equals(nameMime)) { - return name.substring(0, lastDot); - } - } - return name; - } - - /** - * Add file extension to name, but only if exact MIME type mapping exists. - */ - private static String addExtension(String mimeType, String name) { - final String extension = MimeTypeMap.getSingleton() - .getExtensionFromMimeType(mimeType); - if (extension != null) { - return name + "." + extension; - } - return name; - } - private void startObserving(File file, Uri notifyUri) { synchronized (mObservers) { DirectoryObserver observer = mObservers.get(file); diff --git a/packages/ExternalStorageProvider/tests/Android.mk b/packages/ExternalStorageProvider/tests/Android.mk new file mode 100644 index 0000000..830731a --- /dev/null +++ b/packages/ExternalStorageProvider/tests/Android.mk @@ -0,0 +1,16 @@ + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := android.test.runner + +LOCAL_PACKAGE_NAME := ExternalStorageProviderTests +LOCAL_INSTRUMENTATION_FOR := ExternalStorageProvider + +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/packages/ExternalStorageProvider/tests/AndroidManifest.xml b/packages/ExternalStorageProvider/tests/AndroidManifest.xml new file mode 100644 index 0000000..ffcd499 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/AndroidManifest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.externalstorage.tests"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.externalstorage" + android:label="Tests for ExternalStorageProvider" /> + +</manifest> diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java new file mode 100644 index 0000000..f980b60 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.externalstorage; + +import static com.android.externalstorage.ExternalStorageProvider.buildUniqueFile; + +import android.os.FileUtils; +import android.provider.DocumentsContract.Document; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import java.io.File; + +@MediumTest +public class ExternalStorageProviderTest extends AndroidTestCase { + + private File mTarget; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mTarget = getContext().getFilesDir(); + FileUtils.deleteContents(mTarget); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + FileUtils.deleteContents(mTarget); + } + + public void testBuildUniqueFile_normal() throws Exception { + assertNameEquals("test.jpg", buildUniqueFile(mTarget, "image/jpeg", "test")); + assertNameEquals("test.jpg", buildUniqueFile(mTarget, "image/jpeg", "test.jpg")); + assertNameEquals("test.jpeg", buildUniqueFile(mTarget, "image/jpeg", "test.jpeg")); + assertNameEquals("TEst.JPeg", buildUniqueFile(mTarget, "image/jpeg", "TEst.JPeg")); + assertNameEquals("test.png.jpg", buildUniqueFile(mTarget, "image/jpeg", "test.png.jpg")); + assertNameEquals("test.png.jpg", buildUniqueFile(mTarget, "image/jpeg", "test.png")); + + assertNameEquals("test.flac", buildUniqueFile(mTarget, "audio/flac", "test")); + assertNameEquals("test.flac", buildUniqueFile(mTarget, "audio/flac", "test.flac")); + assertNameEquals("test.flac", buildUniqueFile(mTarget, "application/x-flac", "test")); + assertNameEquals("test.flac", buildUniqueFile(mTarget, "application/x-flac", "test.flac")); + } + + public void testBuildUniqueFile_unknown() throws Exception { + assertNameEquals("test", buildUniqueFile(mTarget, "application/octet-stream", "test")); + assertNameEquals("test.jpg", buildUniqueFile(mTarget, "application/octet-stream", "test.jpg")); + assertNameEquals(".test", buildUniqueFile(mTarget, "application/octet-stream", ".test")); + + assertNameEquals("test", buildUniqueFile(mTarget, "lolz/lolz", "test")); + assertNameEquals("test.lolz", buildUniqueFile(mTarget, "lolz/lolz", "test.lolz")); + } + + public void testBuildUniqueFile_dir() throws Exception { + assertNameEquals("test", buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test")); + new File(mTarget, "test").mkdir(); + assertNameEquals("test (1)", buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test")); + + assertNameEquals("test.jpg", buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test.jpg")); + new File(mTarget, "test.jpg").mkdir(); + assertNameEquals("test.jpg (1)", buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test.jpg")); + } + + public void testBuildUniqueFile_increment() throws Exception { + assertNameEquals("test.jpg", buildUniqueFile(mTarget, "image/jpeg", "test.jpg")); + new File(mTarget, "test.jpg").createNewFile(); + assertNameEquals("test (1).jpg", buildUniqueFile(mTarget, "image/jpeg", "test.jpg")); + new File(mTarget, "test (1).jpg").createNewFile(); + assertNameEquals("test (2).jpg", buildUniqueFile(mTarget, "image/jpeg", "test.jpg")); + } + + private static void assertNameEquals(String expected, File actual) { + assertEquals(expected, actual.getName()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java index f9a68d0..479c982 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java @@ -67,6 +67,7 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC private final UserManager mUserManager; private final Receiver mReceiver = new Receiver(); + private NetworkControllerImpl mNetworkController; private boolean mScanning; private int mCurrentUser; @@ -77,6 +78,10 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC mCurrentUser = ActivityManager.getCurrentUser(); } + void setNetworkController(NetworkControllerImpl networkController) { + mNetworkController = networkController; + } + public boolean canConfigWifi() { return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, new UserHandle(mCurrentUser)); @@ -181,7 +186,6 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC ap.isConfigured = config != null; ap.networkId = config != null ? config.networkId : AccessPoint.NO_NETWORK; ap.ssid = ssid; - ap.iconId = ICONS[level]; // Connected if either: // -The network ID in the active WifiInfo matches this network's ID. // -The network is ephemeral (no configuration) but the SSID matches. @@ -189,7 +193,13 @@ public class AccessPointControllerImpl implements NetworkController.AccessPointC && ap.networkId == connectedNetworkId) || (ap.networkId == WifiConfiguration.INVALID_NETWORK_ID && wifiInfo != null && ap.ssid.equals(trimDoubleQuotes(wifiInfo.getSSID()))); - ap.level = level; + if (ap.isConnected && mNetworkController != null) { + // Ensure we have the connected network's RSSI. + ap.level = mNetworkController.getConnectedWifiLevel(); + } else { + ap.level = level; + } + ap.iconId = ICONS[ap.level]; // Based on Settings AccessPoint#getSecurity, keep up to date // with better methods of determining no security or not. ap.hasSecurity = scanResult.capabilities.contains("WEP") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 5a97c75..f3a04b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -82,7 +82,7 @@ public class NetworkControllerImpl extends BroadcastReceiver final WifiSignalController mWifiSignalController; @VisibleForTesting final MobileSignalController mMobileSignalController; - private final AccessPointController mAccessPoints; + private final AccessPointControllerImpl mAccessPoints; private final MobileDataControllerImpl mMobileDataController; // bluetooth @@ -154,6 +154,7 @@ public class NetworkControllerImpl extends BroadcastReceiver // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it updateAirplaneMode(true); + mAccessPoints.setNetworkController(this); } private void registerListeners() { @@ -178,6 +179,10 @@ public class NetworkControllerImpl extends BroadcastReceiver mContext.unregisterReceiver(this); } + public int getConnectedWifiLevel() { + return mWifiSignalController.getState().level; + } + @Override public AccessPointController getAccessPointController() { return mAccessPoints; diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 8b3739d..2e64640 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3768,8 +3768,8 @@ public class ConnectivityService extends IConnectivityManager.Stub int notificationType) { if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE) { Intent intent = new Intent(); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK, nri.request); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, networkAgent.network); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.request); sendIntent(nri.mPendingIntent, intent); } // else not handled diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3e6d15a..57a89679 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4285,6 +4285,14 @@ public class PackageManagerService extends IPackageManager.Stub { boolean updatedPkgBetter = false; // First check if this is a system package that may involve an update if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { + // If new package is not located in "/system/priv-app" (e.g. due to an OTA), + // it needs to drop FLAG_PRIVILEGED. + if (locationIsPrivileged(scanFile)) { + updatedPkg.pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED; + } else { + updatedPkg.pkgFlags &= ~ApplicationInfo.FLAG_PRIVILEGED; + } + if (ps != null && !ps.codePath.equals(scanFile)) { // The path has changed from what was last scanned... check the // version of the new path against what we have stored to determine @@ -4302,12 +4310,6 @@ public class PackageManagerService extends IPackageManager.Stub { + " to " + scanFile); updatedPkg.codePath = scanFile; updatedPkg.codePathString = scanFile.toString(); - // This is the point at which we know that the system-disk APK - // for this package has moved during a reboot (e.g. due to an OTA), - // so we need to reevaluate it for privilege policy. - if (locationIsPrivileged(scanFile)) { - updatedPkg.pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED; - } } updatedPkg.pkg = pkg; throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, null); @@ -7202,7 +7204,9 @@ public class PackageManagerService extends IPackageManager.Stub { // If the original was granted this permission, we take // that grant decision as read and propagate it to the // update. - allowed = true; + if (sysPs.isPrivileged()) { + allowed = true; + } } else { // The system apk may have been updated with an older // version of the one on the data partition, but which diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index db0f53b..be3251c 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -224,6 +224,7 @@ public class UserManagerService extends IUserManager.Stub { |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); + initDefaultGuestRestrictions(); readUserListLocked(); // Prune out any partially created/partially removed users. ArrayList<UserInfo> partials = new ArrayList<UserInfo>(); @@ -469,7 +470,7 @@ public class UserManagerService extends IUserManager.Stub { private void initDefaultGuestRestrictions() { if (mGuestRestrictions.isEmpty()) { mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true); - writeUserListLocked(); + mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true); } } @@ -653,8 +654,15 @@ public class UserManagerService extends IUserManager.Stub { } } } else if (name.equals(TAG_GUEST_RESTRICTIONS)) { - mGuestRestrictions.clear(); - readRestrictionsLocked(parser, mGuestRestrictions); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.END_TAG) { + if (type == XmlPullParser.START_TAG) { + if (parser.getName().equals(TAG_RESTRICTIONS)) { + readRestrictionsLocked(parser, mGuestRestrictions); + } + break; + } + } } } } |