18e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project/* 28e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. 38e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * 48e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * Redistribution and use in source and binary forms, with or without 58e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * modification, are permitted provided that the following conditions 68e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * are met: 78e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * 1. Redistributions of source code must retain the above copyright 88e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * notice, this list of conditions and the following disclaimer. 98e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * 2. Redistributions in binary form must reproduce the above copyright 108e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * notice, this list of conditions and the following disclaimer in the 118e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * documentation and/or other materials provided with the distribution. 128e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * 138e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 148e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 158e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 168e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 178e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 188e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 198e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 208e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 218e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 228e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 238e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 248e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project */ 258e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 268e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "config.h" 278e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "InsertLineBreakCommand.h" 288e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 298e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "CSSMutableStyleDeclaration.h" 308e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "Document.h" 318e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "Frame.h" 325f1ab04193ad0130ca8204aadaceae083aca9881Feng Qian#include "HTMLElement.h" 338f72e70a9fd78eec56623b3a62e68f16b7b27e28Feng Qian#include "HTMLNames.h" 348f72e70a9fd78eec56623b3a62e68f16b7b27e28Feng Qian#include "Range.h" 358f72e70a9fd78eec56623b3a62e68f16b7b27e28Feng Qian#include "RenderObject.h" 368e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "Text.h" 378e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "VisiblePosition.h" 388e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "htmlediting.h" 398e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project#include "visible_units.h" 408e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 418e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Projectnamespace WebCore { 428e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 438e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Projectusing namespace HTMLNames; 448e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 458e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source ProjectInsertLineBreakCommand::InsertLineBreakCommand(Document* document) 468e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project : CompositeEditCommand(document) 478e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project{ 488e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project} 498e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 508e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Projectbool InsertLineBreakCommand::preservesTypingStyle() const 518e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project{ 528e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project return true; 538e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project} 548e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 55635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Projectvoid InsertLineBreakCommand::insertNodeAfterPosition(Node* node, const Position& pos) 568e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project{ 578e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Insert the BR after the caret position. In the case the 588e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // position is a block, do an append. We don't want to insert 598e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // the BR *after* the block. 60635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Project Element* cb = pos.node()->enclosingBlockFlowElement(); 618e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (cb == pos.node()) 628e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project appendNode(node, cb); 638e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project else 648e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project insertNodeAfter(node, pos.node()); 658e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project} 668e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 67635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Projectvoid InsertLineBreakCommand::insertNodeBeforePosition(Node* node, const Position& pos) 688e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project{ 698e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Insert the BR after the caret position. In the case the 708e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // position is a block, do an append. We don't want to insert 718e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // the BR *before* the block. 72635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Project Element* cb = pos.node()->enclosingBlockFlowElement(); 738e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (cb == pos.node()) 748e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project appendNode(node, cb); 758e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project else 768e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project insertNodeBefore(node, pos.node()); 778e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project} 788e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 798e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project// Whether we should insert a break element or a '\n'. 808e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Projectbool InsertLineBreakCommand::shouldUseBreakElement(const Position& insertionPos) 818e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project{ 828e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // An editing position like [input, 0] actually refers to the position before 838e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // the input element, and in that case we need to check the input element's 848e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // parent's renderer. 858e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project Position p(rangeCompliantEquivalent(insertionPos)); 868e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project return p.node()->renderer() && !p.node()->renderer()->style()->preserveNewline(); 878e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project} 888e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 898e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Projectvoid InsertLineBreakCommand::doApply() 908e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project{ 918e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project deleteSelection(); 928f72e70a9fd78eec56623b3a62e68f16b7b27e28Feng Qian VisibleSelection selection = endingSelection(); 938e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (selection.isNone()) 948e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project return; 958e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 968e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project VisiblePosition caret(selection.visibleStart()); 978e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project Position pos(caret.deepEquivalent()); 988e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 998e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project pos = positionAvoidingSpecialElementBoundary(pos); 1008e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1018e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project pos = positionOutsideTabSpan(pos); 1028e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1038e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project RefPtr<Node> nodeToInsert; 1048e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (shouldUseBreakElement(pos)) 1058e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project nodeToInsert = createBreakElement(document()); 1068e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project else 1078e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project nodeToInsert = document()->createTextNode("\n"); 1088e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1098e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // FIXME: Need to merge text nodes when inserting just after or before text. 1108e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1115f1ab04193ad0130ca8204aadaceae083aca9881Feng Qian if (isEndOfParagraph(caret) && !lineBreakExistsAtVisiblePosition(caret)) { 1128e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project bool needExtraLineBreak = !pos.node()->hasTagName(hrTag) && !pos.node()->hasTagName(tableTag); 1138e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1148e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project insertNodeAt(nodeToInsert.get(), pos); 1158e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1168e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (needExtraLineBreak) 117635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Project insertNodeBefore(nodeToInsert->cloneNode(false), nodeToInsert); 1188e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1198e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project VisiblePosition endingPosition(Position(nodeToInsert.get(), 0)); 1208f72e70a9fd78eec56623b3a62e68f16b7b27e28Feng Qian setEndingSelection(VisibleSelection(endingPosition)); 1215f1ab04193ad0130ca8204aadaceae083aca9881Feng Qian } else if (pos.deprecatedEditingOffset() <= caretMinOffset(pos.node())) { 1228e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project insertNodeAt(nodeToInsert.get(), pos); 1238e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1248e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Insert an extra br or '\n' if the just inserted one collapsed. 1258e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (!isStartOfParagraph(VisiblePosition(Position(nodeToInsert.get(), 0)))) 1268e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project insertNodeBefore(nodeToInsert->cloneNode(false).get(), nodeToInsert.get()); 1278e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 128231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block setEndingSelection(VisibleSelection(positionInParentAfterNode(nodeToInsert.get()), DOWNSTREAM)); 129635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Project // If we're inserting after all of the rendered text in a text node, or into a non-text node, 130635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Project // a simple insertion is sufficient. 1315f1ab04193ad0130ca8204aadaceae083aca9881Feng Qian } else if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.node()) || !pos.node()->isTextNode()) { 1328e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project insertNodeAt(nodeToInsert.get(), pos); 133231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block setEndingSelection(VisibleSelection(positionInParentAfterNode(nodeToInsert.get()), DOWNSTREAM)); 134237faca5ff0377c7fda5c4dcc0f287e67fe66091Steve Block } else if (pos.node()->isTextNode()) { 1358e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Split a text node 136635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Project Text* textNode = static_cast<Text*>(pos.node()); 1375f1ab04193ad0130ca8204aadaceae083aca9881Feng Qian splitTextNode(textNode, pos.deprecatedEditingOffset()); 138635860845790a19bf50bbc51ba8fb66a96dde068The Android Open Source Project insertNodeBefore(nodeToInsert, textNode); 1398e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project Position endingPosition = Position(textNode, 0); 1408e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1418e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Handle whitespace that occurs after the split 1428e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project updateLayout(); 1438e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (!endingPosition.isRenderedCharacter()) { 144231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block Position positionBeforeTextNode(positionInParentBeforeNode(textNode)); 1458e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Clear out all whitespace and insert one non-breaking space 1468e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project deleteInsignificantTextDownstream(endingPosition); 1478e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); 1488e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Deleting insignificant whitespace will remove textNode if it contains nothing but insignificant whitespace. 1498e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (textNode->inDocument()) 1508e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project insertTextIntoNode(textNode, 0, nonBreakingSpaceString()); 1518e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project else { 1528e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project RefPtr<Text> nbspNode = document()->createTextNode(nonBreakingSpaceString()); 1538e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project insertNodeAt(nbspNode.get(), positionBeforeTextNode); 1548e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project endingPosition = Position(nbspNode.get(), 0); 1558e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project } 1568e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project } 1578e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1588f72e70a9fd78eec56623b3a62e68f16b7b27e28Feng Qian setEndingSelection(VisibleSelection(endingPosition, DOWNSTREAM)); 1598e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project } 1608e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1618e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Handle the case where there is a typing style. 1628e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1638e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project CSSMutableStyleDeclaration* typingStyle = document()->frame()->typingStyle(); 1648e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1658e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project if (typingStyle && typingStyle->length() > 0) { 1668e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Apply the typing style to the inserted line break, so that if the selection 1678e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // leaves and then comes back, new input will have the right style. 1688e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // FIXME: We shouldn't always apply the typing style to the line break here, 1698e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // see <rdar://problem/5794462>. 1708f72e70a9fd78eec56623b3a62e68f16b7b27e28Feng Qian applyStyle(typingStyle, firstDeepEditingPositionForNode(nodeToInsert.get()), lastDeepEditingPositionForNode(nodeToInsert.get())); 1718e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // Even though this applyStyle operates on a Range, it still sets an endingSelection(). 1728f72e70a9fd78eec56623b3a62e68f16b7b27e28Feng Qian // It tries to set a VisibleSelection around the content it operated on. So, that VisibleSelection 1738e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // will either (a) select the line break we inserted, or it will (b) be a caret just 1748e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // before the line break (if the line break is at the end of a block it isn't selectable). 1758e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // So, this next call sets the endingSelection() to a caret just after the line break 1768e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project // that we inserted, or just before it if it's at the end of a block. 1778e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project setEndingSelection(endingSelection().visibleEnd()); 1788e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project } 1798e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1808e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project rebalanceWhitespace(); 1818e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project} 1828e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project 1838e35f3cfc7fba1d1c829dc557ebad6409cbe16a2The Android Open Source Project} 184